<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>박선우의 블로그</title><description>박선우의 블로그 글들</description><link>https://ericswpark.com/blog/</link><language>ko</language><item><title>Let&apos;s Encrypt와 함께 HP 프린터에 SSL 인증서를 설치하기</title><link>https://ericswpark.com/ko/blog/2026/2026-06-09-hp-lets-encrypt/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2026/2026-06-09-hp-lets-encrypt/</guid><pubDate>Tue, 09 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;del&gt;레거시 시스템이 싫어요&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;이 가이드는 HP DeskJet 3637에서 테스트되었습니다. 사용하시는 프린터 모델에 내장된 웹 서버가
얼마나 낙후되었는지에 따라 절차가 다를 수 있습니다. 참고로, 이 글을 작성하는 시점에 3637의
펌웨어 버전은 &lt;code&gt;SWP2FN2223AR&lt;/code&gt;이며 빌드 날짜는 2022년 5월 30일입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;아직 안 하셨다면 프린터에 정적 IP 주소를 설정하세요.
(나중에 DHCP 리스가 만기되서 드라이버를 6-7번 재설치하고 여럿 종교에 제사를 지내는 수고를
덜어주기도 하죠)&lt;/li&gt;
&lt;li&gt;정적 IP 주소를 가리키는 DNS A 레코드를 생성합니다 (예를 들어, print.example.com -&gt;
192.168.0.150)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;certbot&lt;/code&gt;를 실행합니다:
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;certbot&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; certonly&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -v&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --manual&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --preferred-challenges&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dns&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -d&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; print.example.com&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --key-type&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; rsa&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
여기서 &lt;code&gt;--key-type rsa&lt;/code&gt; 플래그가 중요한데, 최신 기본값은 EC 키 타입이기 때문입니다.
물론, 산업 폐기물 HP가 그따위 알아볼리 없으니 어쩔 수 없죠. (망해라 HP)&lt;/li&gt;
&lt;li&gt;DNS 챌런지 관련 프롬프트를 따라 완성합니다&lt;/li&gt;
&lt;li&gt;이제 개별 인증서 파일이 다운로드되면, &lt;code&gt;certbot&lt;/code&gt;이 해당 파일들을 어디에 저장해두었는지
확인해둡니다. 이제 PKCS#12 파일로 번들을 해줘야 합니다. 이를 위해 &lt;code&gt;openssl&lt;/code&gt;을
사용합니다:
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;openssl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pkcs12&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -export&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -out&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; printer.pfx&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -inkey&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; privkey.pem&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; cert.pem&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
여기서 내보내기 비밀번호를 물어보는데, 아무 임시 비밀번호를 입력하면 됩니다. 조금 있다가
다시 입력해야 하기 때문에 잠시 기억해둡니다.&lt;/li&gt;
&lt;li&gt;프린터의 IP(나 아까 설정한 도메인)에 접속하고, 다음 경로로 진입합니다: “Network &gt;
Advanced Settings &gt; Certificates”&lt;/li&gt;
&lt;li&gt;“Configure”를 클릭한 후, “Import a Certificate and Private Key”를 선택합니다.&lt;/li&gt;
&lt;li&gt;5번에서 만든 &lt;code&gt;printer.pfx&lt;/code&gt; 파일을 선택한 후, 아까 입력했던 임시 비밀번호를 입력한 후,
“Mark private key as exportable”을 체크하지 않고 내버려둡니다.&lt;/li&gt;
&lt;li&gt;“Finish”를 클릭하여 저장합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이제 3개월마다 반복하는 절차만 남았군요.&lt;/p&gt;
&lt;p&gt;물론, 이 모든 게 다 webUI단에서 이루어지도록 했을 수도 있는데, 그렇게 사용자-친화적이라면
실제로 HP 프린터라고 할 수 있을까요?&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;초안에 적는 걸 깜빡해서 조금 더 끄적여봅니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Q: HP를 굳이?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A: 페북에서 $10밖에 안 해서 샀습니다. 잉크 카트리지가 더 비싸더라고요.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Q: HP webUI 상 “Certificate Request” 옵션을 사용하면 안되나요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A: 그걸 쓰면 공개 키를 만들 때 지수(exponent)를 3으로 설정해버리는데, 이 세상에
정신나가지 않고서야 그걸 받아주는 CA가 없기 때문이죠. 어떻게 &lt;del&gt;고통스럽게&lt;/del&gt;
알아냈는지는 물어보지 말아주세요.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>JuiceSSH가 유료 기능들을 뺏어갔습니다</title><link>https://ericswpark.com/ko/blog/2026/2026-06-03-juicessh/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2026/2026-06-03-juicessh/</guid><pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;오늘 휴대폰에서 JuiceSSH를 사용하다가 지난 몇 달 동안 사용하던 기능들이 갑자기 사라진 것을
확인했습니다. 무료와 유료 기능들을 비교하는 화면이 뜨길래, “이미 구매함” 버튼을 누르면
Play 라이선스 API/서버를 확인하고 다시 원상복구될 줄 알았죠.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;구매 확인 스크린샷&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1440&quot; height=&quot;2978&quot; src=&quot;/_astro/purchase-verify-screen.BWc_Jove_Z15TU3I.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;하지만 아무런 반응도 없길래, 라이선스 오류인가 했습니다. 가끔씩 Play 라이선스 API가 이러는
경우가 있는데, 대부분 다시 구매하려고 하면 그제서야 이미 구매한 것을 눈치채는 경우가 있어
“구매” 버튼을 시도해봤습니다. 하지만 뜨는 창에서 아이템이 “더 이상 구매할 수 없다”고
표시되었죠:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;아이템이 더 이상 없다는 문구&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1440&quot; height=&quot;915&quot; src=&quot;/_astro/no-longer-available.SCpi-HqY_ZrIBll.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.reddit.com/r/androidapps/comments/1pqzq46/juice_ssh_delisted_december_2025/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Reddit에 의하면, 2025년 12월부터 JuiceSSH가 Play 스토어에서 제거되었다고 합니다&lt;/a&gt;.
마지막 릴리즈가 2021년에 이루어진 것으로 보아, 하도 오랫동안 업데이트를 하지 않아 구글이 그냥
어플을 내린 듯 합니다. 만약 안드로이드 개발자라면 가끔씩 구글이 보내는 이메일에서 어플을 새
정책에 “부합”하도록 업데이트하라는 지시사항을 보셨을 텐데, 시간 안에 이걸 완료하지 않으면
어플이 자동으로 출시 해제되어 버리거든요.&lt;/p&gt;
&lt;p&gt;급할 때 사용하기 편리했고, 유료 기능들도 잘 사용하고 있었는데 갑자기 이렇게 되버리니 아쉬움만
남아버리네요. 사용자가 구매한 제품을 이렇게 다시 회수해 갈 수 없어야 하는데, 라이선스를
확인하는 서버를 내려버리니까 결국에는 정당하게 구매한 기능들을 사용할 수 없게 된 것이죠.&lt;/p&gt;
&lt;p&gt;그리고 이게 최근에 일어난 것도 아닌 것이, Hacker News와 Reddit에서 몇 달 전부터 이 문제와
관련해서 글을 찾아볼 수 있었습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=46768909&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://news.ycombinator.com/item?id=46768909&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/androidapps/comments/1q321p4/juicessh_pro_features_lost/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://www.reddit.com/r/androidapps/comments/1q321p4/juicessh_pro_features_lost/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;개발진이 마지막으로 업데이트를 출시해서, 서버를 운영할 수 없다면 라이선스 확인 단계를
없애버리거나, 그것조차도 하기 싫다면 어플 자체를 오픈소스로 공개하면 좋지 않았을까 합니다.
하지만 그냥 어플을 내려버리고 심지어 웹사이트도 모조리 삭제한 것 같습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://juicessh.com/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://juicessh.com/&lt;/a&gt; (죽은 링크)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sonelli.com/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://sonelli.com/&lt;/a&gt; (죽은 링크)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/li-yifei/JuiceSSHOfflineProFix/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;이런 프로젝트&lt;/a&gt;를 사용해서 라이선스 확인 단계를 패치하여 없애는 방법도 있지만,
Xposed를 사용해야 하고, 이 어플 자체가 업데이트를 오랫동안 받지 않아 보안에 있어서 취약할
수도 있는데 굳이 다시 사용하기 위해 휴대폰을 개조하기는 싫거든요.&lt;/p&gt;
&lt;p&gt;유료 안드로이드 어플에 평이 그렇게 좋지도 않은데 이렇게 어플이 사라지니까 추후에도 더 인식이
안 좋아지지 않을까 합니다. 적어도 Apple의 앱스토어에서는, 어플이 라이선스와 서버측에서
뭔가 이상한 것을 하지 않는 이상, 출시 해제된 유료 어플들을 다시 재다운로드하고 출판된 상태
그대로 사용할 수 있으니까요. 물론, 서버측 기능들은 더 이상 동작하지 않겠지만, 라이선스 확인은
Apple의 앱스토어를 통해 이루어지기 때문에 유료 기능들이 계속 동작할 수 있게 해주기
때문입니다.&lt;/p&gt;
&lt;p&gt;짜증나는 부분은 &lt;a href=&quot;https://developer.android.com/google/play/licensing/overview&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;이게 안드로이드 상에서도 가능하다는 점입니다&lt;/a&gt;. 어플이 클라이언트단에서
라이선스를 확인하면 되는데, JuiceSSH 개발진이 이렇게 하지 않고 서버측에서 확인하도록 어플을
제작하여, 결국 이렇게 라이선스 확인이 실패하는 문제가 발생하였죠.&lt;/p&gt;
&lt;p&gt;물론 이제 이 어플을 더 이상 사용할 수 없으니 대체제를 찾아볼 시간입니다. Termius라는 어플을
확인했을 때 괜찮아 보였는데, 오픈소스가 아니고, JuiceSSH와 비슷하게 추후에 유료로 구매한
기능들이 사라질 수 있는 가능성이 싫어 선택하지 않았습니다.&lt;/p&gt;
&lt;p&gt;다음으로 가장 좋은 선택은 &lt;a href=&quot;https://connectbot.org/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;ConnectBot&lt;/a&gt;인 것 같은데, JuiceSSH에서 사용했던 기능들을
거의 대부분 갖춘 것 같거든요. Mosh 지원이 없지만, &lt;a href=&quot;https://github.com/connectbot/connectbot/pull/2169&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;이 PR&lt;/a&gt;에서 지원에 관련한 개발 활동이
진행 중이니 추후에 추가되지 않을까 싶습니다. 호스트 설정값과 키값들을 이동하는데 반나절이
걸리겠지만, 어플이 오픈소스니 딱 한번만 하면 추후에는 다시는 이런 일이 없겠죠!&lt;/p&gt;
&lt;p&gt;글을 마치면서: 꽤 짜증나는 문제고, 원래 개발진인 Paul과 Tom Maddox는 반성해야 하겠지만,
적어도 대체제인 ConnectBot이 있으니 그나마 다행인 듯 합니다.&lt;/p&gt;</content:encoded></item><item><title>제 학사모에 Rust가 돌아갑니다</title><link>https://ericswpark.com/ko/blog/2026/2026-05-12-my-graduation-cap-runs-rust/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2026/2026-05-12-my-graduation-cap-runs-rust/</guid><pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;한번도 졸업해 본 적이 없습니다. 적어도, 대학에서요. 그래서 졸업 복장하고 학사모 그런 건
전혀 몰랐습니다.&lt;/p&gt;
&lt;p&gt;재미있는 사실 #1: 미국에선 졸업 복장과 학사모를 대여합니다. 그리고 물론 반납해야 하죠. 게다가
비싸기까지 하고요! 제 복장을 대여하는데 $94를 내야 했는데, 제조 비용이 그것보다 훨씬
저렴할 수 밖에 없으니 말도 안 된다고 생각합니다. 그럼 아예 대여를 하지 않으면요? 그럼
졸업식에 참석을 못 하게 합니다. 그래서 대여가 필수죠. 그리고 복장과 학사모를 아예 소장용으로
구입하는 옵션조차 없구요.&lt;/p&gt;
&lt;p&gt;…생각해보니 그렇게 재밌지는 않군요. 그러면:&lt;/p&gt;
&lt;p&gt;재미있는 사실 #2: 졸업할 때, 앞에 계신 분이 학사모에 달린 술장식을 오른쪽에서 왼쪽으로
움직여주는데, 이게 상징하는게 무엇이냐면… 뭘까요? 왜 왼쪽에서 오른쪽은 안될까요?
왼손잡이는요?! 왜 졸업식들은 모두 왼손잡이들을 차별하는 걸까요?!&lt;/p&gt;
&lt;p&gt;어쨌든. 다시 본론으로 돌아와서 재밌는 사실 #2를 생각해보고 있었는데, 앞에 분이 술장식을
움직였을때 졸업모가 불타면 신기하지 않을까 생각해봤습니다. 하지만 대여 계약서 제 98.c.2
조항이 아마도 이걸 금지할 거고, Purdue에서 무대에 불을 지르면 그렇게 달가워하지 않으리라
생각합니다.&lt;/p&gt;
&lt;p&gt;흠. 그럼 술장식이 움직이는 것을 감지하고 졸업모의 밑면에 불이 켜지도록 하는 장치는요?&lt;/p&gt;
&lt;p&gt;그걸로 곧바로 이 정신나간 아이디어를 만들어봤습니다. 그리고 결과물은 (제가 생각했을땐) 참으로
아름답군요:&lt;/p&gt;
&lt;div class=&quot;w-full aspect-video&quot;&gt; &lt;iframe class=&quot;w-full h-full&quot; src=&quot;https://www.youtube.com/embed/w_5cP-b8fHY&quot; title=&quot;YouTube video player&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt; &lt;/div&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;/h2&gt;
&lt;h3 id=&quot;어떤-부품들을-사용했나요&quot;&gt;어떤 부품들을 사용했나요?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Digispark ATtiny85 1개&lt;/li&gt;
&lt;li&gt;WS2812B LED 48개&lt;/li&gt;
&lt;li&gt;죽은 Apple USB-C-to-C 케이블에서 잘라낸 전선&lt;/li&gt;
&lt;li&gt;술장식 이동 감지용 자기 리드 스위치와 자석 (데모에선 나타나지 않습니다)&lt;/li&gt;
&lt;li&gt;USB-C Power Delivery 트리거 보드&lt;/li&gt;
&lt;li&gt;보조배터리 (및 USB-C 케이블)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;이걸-만드는데-얼마나-걸렸나요&quot;&gt;이걸 만드는데 얼마나 걸렸나요?!&lt;/h3&gt;
&lt;p&gt;코드 짜는데 약 2시간 정도가 걸렸습니다. 대부분의 시간은 &lt;code&gt;avr-hal&lt;/code&gt;와 &lt;code&gt;ws2812-avr&lt;/code&gt;
라이브러리가 ATtiny85를 바로 지원하지 않아 거기에 많이 할애하였죠. GitHub 레포지터리를
fork해서 몇 가지를 빠르게 패치해야 했는데, 예를 들자면 기본 클럭 속도를 16 MHz로 설정해야
정상적으로 컴파일되었습니다.&lt;/p&gt;
&lt;p&gt;아마도 Rust를 사용하지 않고 Arduino 라이브러리를 사용했거나, 다른 보드를 사용했다면 조금
더 쉬웠을 수도 있습니다. 하지만 이 글 제목을 그대로 쓰고 싶었고, ESP32 보드는 너무 과하고
학사모에 제대로 붙어있지 않았을 것 같아 이렇게 진행했습니다.&lt;/p&gt;
&lt;p&gt;하드웨어 쪽이 제일 오랜 시간이 걸렸는데, 3시간 넘게 소요되었습니다. 누군가 하드웨어가 쉽다고
얘기하면, 틀렸거나, 거짓말을 하고 있거나 커스텀 하드웨어 프로젝트를 만들어 본 적이 없는
사람입니다!&lt;/p&gt;
&lt;h3 id=&quot;이걸-실제로-졸업식에-착용할-건가요&quot;&gt;이걸 실제로 졸업식에 착용할 건가요?&lt;/h3&gt;
&lt;p&gt;미쳤냐고요?&lt;/p&gt;
&lt;p&gt;생각해 봤는데 너무 촌스러워서 착용까진 안 할 예정입니다. 보고 있는 느낌이 어린 애들이 게이밍
컴퓨터를 떠올리거나, 꼰대들이 발작을 떠올리는 느낌이랄까요.&lt;/p&gt;
&lt;h3 id=&quot;발작을-얘기하니까-아쉽게-놓친-아이디어가-있는-것-같은데&quot;&gt;발작을 얘기하니까, 아쉽게 놓친 아이디어가 있는 것 같은데…&lt;/h3&gt;
&lt;p&gt;…놓쳤다니요?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;경고: 해당 둥영상에는 섬광 효과가 포함되어 있습니다. 스크린 리더로 이걸 들으신다면 둥영상을
시청하지 않는 것이 좋습니다!&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;w-full aspect-video&quot;&gt; &lt;iframe class=&quot;w-full h-full&quot; src=&quot;https://www.youtube.com/embed/mcMLJuRSiiM&quot; title=&quot;YouTube video player&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt; &lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=uQRIvXymJZI&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;del&gt;WKUK 못지않게&lt;/del&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;코드는요&quot;&gt;코드는요?&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ericswpark/gradcap-rs&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://github.com/ericswpark/gradcap-rs&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>한국 인터넷 검열, 그리고 다가오는 끝</title><link>https://ericswpark.com/ko/blog/2026/2026-01-11-korea-internet-censors/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2026/2026-01-11-korea-internet-censors/</guid><pubDate>Sun, 11 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이 글을 어른인 제가 인터넷을 사용하는데 제약을 두는 망할 “심의위원회”에 바칩니다.&lt;/p&gt;
&lt;p&gt;우리나라는 독재 정권도 아니고 중국의 황금 방패도 없지만,
&lt;a href=&quot;https://en.wikipedia.org/wiki/Internet_censorship_in_South_Korea&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;인터넷을 검열하는 것은 널리 알려져 있습니다&lt;/a&gt;.
외국인의 경우에는 막 도착해 호텔방에서 성인 사이트에 접속하려 하다가 충격을 주게 마련이죠.&lt;/p&gt;
&lt;p&gt;물론, 이렇게 정책이 되어 있는 게 &lt;em&gt;맞는가, 아닌가&lt;/em&gt; 적으면 글이 너무 길어져 안 읽으실 듯합니다. (물론
이미 창을 닫으셨겠지만 읽고 있다고 가정해 보겠습니다. 아직도 읽고 계신다면 감사하고요.) 하지만 제
생각에는 이렇게 검열을 진행하게 된다면 권한이 악용되거나 확대하여 해석되는 경우가 있을 것이고, 이미
성인으로서 법적으로 아무런 문제가 없는 사이트 접속도 차단되고 있어 &lt;strong&gt;벌써 문제가 되는 상황&lt;/strong&gt;입니다.
따라서 이 방식은 적합하지 않다고 생각하고 다음에 폐지되어야 한다고 생각하지만, 일단 한국에선 어떻게
인터넷이 검열되는지 알아보겠습니다.&lt;/p&gt;
&lt;h2 id=&quot;예전에-사용했던-한국의-인터넷-검열-방식&quot;&gt;(예전에 사용했던) 한국의 인터넷 검열 방식&lt;/h2&gt;
&lt;p&gt;예전에는 한국의 주요 통신사들이 DNS를 조작하는 방식으로 인터넷을 검열했었습니다. 다른 국가들이
사용했던 주 방식인데, 예시로 Google.com에 접속하려고 했을 때 구글이 (어떤 심사를 통해) 차단된
사이트 목록에 속한다면, 통신사의 DNS 서버에서 구글의 IP가 아닌 다른 IP로 응답해 사용자를 가로채는
방식이었습니다.&lt;/p&gt;
&lt;p&gt;물론, 암호화된 DNS의 보급과 브라우저의 HTTPS 검증을 통해 (특히 HSTS가 활성화되어 있다면) 한국
정부가 만든 “경고” 페이지를 더 이상 볼 수 없게 되었죠.&lt;/p&gt;
&lt;p&gt;기억을 되살리기 위해, 만약 예전에 이러한 검열 방식에 걸리게 되면 &lt;a href=&quot;http://warning.or.kr/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;다음과 같은 페이지&lt;/a&gt;가
나타났습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;대한민국 정부 인터넷 검열 경고 페이지&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1662&quot; height=&quot;810&quot; src=&quot;/_astro/warning.DN__VkpT_aH9Jb.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;디자인도 끔찍하고, (“Warning” 문구를 제외하면) 모두 한국어로 되어 있어 외국인이 이해하기 어려우며,
무슨 심의를 거쳐 불법 또는 유해 내용을 담고 있는 사이트를 “합법적”으로 차단한다는 말도 안 되는 소리를
하는데, 물론 여기에서 “유해 내용”이란 성인이면 봐도 상관없지만 아동 및 청소년이 실수로 접속해서 볼 수도
있기 때문에 정부가 부모를 대신해서 차단해 주는 것도 포함됩니다. 부모님들이 실제로 기기의 연령 제한 기능을
사용하는 것은 너무 어렵고 일반 사용자라면 어떻게 하는지 모르니까, 당연히 전부 다 못 접속하는 것이
합당하죠!&lt;/p&gt;
&lt;p&gt;위에 설명했듯이, DNS 요청을 평문 포트에서 주고받는 점이 끔찍하다는 것을 인지한 후, DNS-over-TLS와
DNS-over-HTTPS와 같은 것을 보급하여 빠르게 패치해나갔습니다. 따라서 한국 통신사들은 DNS 쿼리를 더
이상 낚아채지 않지만, 그래도 혹시 모를 다른 짓거리를 할 수도 있으니 다른 DNS 서버를 사용하는 것이 좋습니다.&lt;/p&gt;
&lt;p&gt;그러면 어떻게 검열 방식이 바뀌었을까요?&lt;/p&gt;
&lt;h2 id=&quot;현재-사용되는-한국의-인터넷-검열-방식&quot;&gt;(현재 사용되는) 한국의 인터넷 검열 방식&lt;/h2&gt;
&lt;p&gt;현시점에선, 통신사들은 실제 HTTPS 요청을 확인합니다.&lt;/p&gt;
&lt;p&gt;서버에 요청을 보내게 될 경우, TLS 연결을 정립하기 위한 통신이 몇 번 이루어집니다. 이 과정에서
사용되는 TLS 확장 중의 하나가 Server Name Indication(SNI)입니다. 이 확장이 필요한 이유는 웹
서버들이 더욱더 reverse proxy의 역할을 하면서 한 서버에 여러 도메인을 제공하는 경우가 늘고 있기
때문에, 정확히 &lt;em&gt;어떤&lt;/em&gt; 도메인을 사용자가 원하는지 알려주는 장치가 필요하기 때문이죠. 따라서 SNI
필드에 도메인 정보가 보내지고, 서버는 도메인에 부합하는 TLS 인증서를 보내어 통신이 이루어지게
됩니다.&lt;/p&gt;
&lt;p&gt;하지만 SNI 필드는 암호화되지 않아 한국(과 다른 나라들)의 통신사들이 무슨 사이트를 접속하려 하는지
확인할 수 있고, 연결을 끊어버릴 수 있습니다. 위의 검열 사이트에 리디렉트를 할 순 없지만, 연결을
중단하기 위해 리셋 패킷을 보낼 수 있죠.&lt;/p&gt;
&lt;h2 id=&quot;그럼-어떻게-우회할-수-있나요&quot;&gt;그럼 어떻게 우회할 수 있나요?&lt;/h2&gt;
&lt;p&gt;쉽고 지루한 해결책은 VPN 또는 프록시 등을 사용하는 게 있지만.&lt;/p&gt;
&lt;p&gt;그 SNI 필드 하나를 가리기 위해 사용하자니 낭비 같기도 하고 귀찮기도 합니다! 다른 해결책이 분명히
있겠죠?&lt;/p&gt;
&lt;p&gt;당연히 있긴 있습니다. 처음에 발표되었을 때의 이름인 Encrypted Server Name Indication (ESNI)가
더욱더 보완되어 가장 최근에 발표된 표준인 Encrypted Client Hello(ECH)에선 TLS 연결을 정립할 때
사용되는 비암호화된 부분을 암호화해 줍니다.&lt;/p&gt;
&lt;p&gt;ECH를 사용하게 된다면, 통신사들이 전송되는 데이터를 감시할 수 없으며 무슨 사이트에 연결하는지 확인할
수 없죠.&lt;/p&gt;
&lt;h2 id=&quot;그럼-지금-바로-ech를-사용할-수-있나요&quot;&gt;그럼 지금 바로 ECH를 사용할 수 있나요?&lt;/h2&gt;
&lt;p&gt;설명하기가 조금 복잡합니다.&lt;/p&gt;
&lt;p&gt;대부분의 주요 브라우저에서 현재 ECH를 지원하고 있는데, 서버들도 역시 지원을 추가해야 사용할 수 있습니다.
지원이 조금씩 확산하고 있지만, 모든 주요 서비스가 지원하기 전까지는 검열을 당하는 몇몇 사이트를
만나실 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;curl&lt;/code&gt;와 같은 클라이언트들은 사용하는 SSL 라이브러리가 ECH를 지원해야 합니다. 예를 들자면, 현재
Arch Linux에 기본으로 제공되는 &lt;code&gt;curl&lt;/code&gt; 바이너리는 ECH를 지원하지 않습니다.&lt;/p&gt;
&lt;p&gt;개인적으로는 이 문제를 제 Rust 프로그램을 개발하면서 맞닥뜨렸는데, 브라우저에선 연결이 성공하는 사이트가
Rust 클라이언트에서는 실패하는 경우가 발생하여 확인하기 시작했습니다. 제가 사용하는 의존성인
&lt;a href=&quot;https://github.com/seanmonstar/reqwest&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;reqwest&lt;/code&gt;&lt;/a&gt;가 최근에 &lt;a href=&quot;https://github.com/rustls/rustls&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;rustls&lt;/code&gt;&lt;/a&gt;로 변경하였는데, 해당
라이브러리는 클라이언트 ECH를 지원하지만 &lt;a href=&quot;https://github.com/seanmonstar/reqwest&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;reqwest&lt;/code&gt;&lt;/a&gt;에서 해당 기능을 어떻게
사용하는지에 대한 문서는 아직 존재하지 않고, 기본으로 활성화되어 있는지도 명시되어 있지 않습니다 (아마
활성화가 되어 있지 않겠죠).&lt;/p&gt;
&lt;p&gt;그래도 ECH가 대중화된다면, 인터넷 연결을 검열하는 방법은 중국처럼 ESNI/ECH 기반의 모든 접속을 차단하는
수밖에 없습니다. 인터넷 대부분을 망가뜨리지 않고선 그렇게 할 수 있는 방법이 없죠.&lt;/p&gt;
&lt;p&gt;언젠간 저 검열 웹사이트도 사라지고 다시는 볼 필요가 없겠죠?&lt;/p&gt;</content:encoded></item><item><title>systemd로 SSH 키 업데이트</title><link>https://ericswpark.com/ko/blog/2025/2025-12-22-ssh-keys-systemd-timer/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2025/2025-12-22-ssh-keys-systemd-timer/</guid><pubDate>Mon, 22 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이 글은 &lt;a href=&quot;/ko/blog/2021/2021-02-01-persistent-access-with-ssh-keys/&quot;&gt;예전에 적었던 블로그 글&lt;/a&gt;의
후속편으로 보실 수 있는데, SSH 키를 지속하도록 만드는 방법을 다루겠습니다. 요즘에는 &lt;code&gt;systemd&lt;/code&gt;가
설치되어 있지 &lt;em&gt;않은&lt;/em&gt; 리눅스 머신을 찾아보기 힘들 정도로 많이 보급되어 있는데, 반대로 &lt;code&gt;crontab&lt;/code&gt;은
안 깔려있는 추세가 증가하고 있죠.&lt;/p&gt;
&lt;p&gt;따라서 해당 머신에서는 그 글에 있는 스크립트를 서비스와 &lt;code&gt;systemd.timer&lt;/code&gt; 유닛 파일을 사용하여 실행하는
방법을 작성하겠습니다. 이는 &lt;code&gt;crontab&lt;/code&gt;이 하는 역할과 완전히 동일합니다.&lt;/p&gt;
&lt;h2 id=&quot;systemd-유닛-파일-생성&quot;&gt;&lt;code&gt;systemd&lt;/code&gt; 유닛 파일 생성&lt;/h2&gt;
&lt;p&gt;SSH 키 업데이트는 사용자 권한으로도 충분히 실행 가능하기에, 사용자 유닛 파일을 생성하겠습니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;mkdir -p ~/.config/systemd/user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;touch ~/.config/systemd/user/ssh-key-update.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 다음 내용을 원하시는 편집기로 붙여넣으세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Unit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Description=Update SSH keys from GitHub&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Service]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ExecStart=/path/to/update-ssh.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다음으로, &lt;code&gt;ssh-key-update&lt;/code&gt; &lt;code&gt;systemd&lt;/code&gt; 서비스를 실행해 줄 &lt;code&gt;timer&lt;/code&gt;를 만들 차례입니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;touch ~/.config/systemd/user/ssh-key-update.timer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다음을 붙여넣습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Unit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Description=Update SSH keys from GitHub&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Timer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;OnBootSec=5min&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;OnUnitActiveSec=5min&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Unit=ssh-key-update.service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Install]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;WantedBy=timers.target&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;타이머-활성화-및-실행&quot;&gt;타이머 활성화 및 실행&lt;/h2&gt;
&lt;p&gt;다음을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;systemctl --user enable --now ssh-key-update.timer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예전처럼 SSH 키가 주기적으로 다운로드되어 저장되는 것을 보실 수 있습니다. 하지만 로그아웃을 하면
업데이트가 멈추는 문제가 아직 있습니다.&lt;/p&gt;
&lt;h2 id=&quot;lingering-활성화&quot;&gt;Lingering 활성화&lt;/h2&gt;
&lt;p&gt;다음을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;loginctl enable-linger&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이는 lingering을 활성화하는데, 그러면 로그인을 하지 않고도 &lt;code&gt;systemd.timer&lt;/code&gt;가 계속해서 실행되도록
해 줍니다.&lt;/p&gt;</content:encoded></item><item><title>Boingo Hotspot 인증포털 문제해결</title><link>https://ericswpark.com/ko/blog/2025/2025-12-21-boingo-hotspot-captive-portal/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2025/2025-12-21-boingo-hotspot-captive-portal/</guid><pubDate>Sun, 21 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;미국 주요 공항에서 사용되는 “Boingo Hotspot” Wi-Fi 네트워크에 연결할 때 겪은 문제 두 개를 해결하는
방법을 소개해 드리겠습니다. 블로그 글로 적는 이유는 작년에도 동일한 문제가 발생했었고, 아마도 인증
페이지 구조를 수정하지 않는 이상 돌아오는 2026년에도 문제가 될 가능성이 높기에 잊지 않도록 적어봅니다.&lt;/p&gt;
&lt;h2 id=&quot;문제-1번--tlshttps를-통한-dnsdns-over-tlshttps-쿼리-불가&quot;&gt;문제 1번 — TLS/HTTPS를 통한 DNS(DNS-over-TLS/HTTPS) 쿼리 불가&lt;/h2&gt;
&lt;p&gt;현재 제 시스템에서는 &lt;code&gt;systemd-resolved&lt;/code&gt;를 통해 TLS 방식의 DNS를 사용 중인데, 미국 시카고
오헤어 공항의 네트워크에 연결하여 &lt;code&gt;dig&lt;/code&gt;로 DNS 쿼리를 시도하면 타임아웃이 계속 발생했습니다.
원인은 바로 네트워크가 TLS/HTTPS 기반의 DNS 쿼리를 전부 막고 일반 DNS 포트 &lt;code&gt;53&lt;/code&gt;번만을 사용하도록
강제하기 때문이죠.&lt;/p&gt;
&lt;p&gt;따라서, 설정 파일 &lt;code&gt;/etc/systemd/resolved.conf&lt;/code&gt;(또는 &lt;code&gt;/etc/systemd/resolved.conf.d/dns-over-tls.conf&lt;/code&gt;)이
다음과 같다면:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Resolve]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;DNS=1.1.1.1#one.one.one.one&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;DNSOverTLS=yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Domains=~.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;도메인을 &lt;code&gt;dig&lt;/code&gt;로 검색을 시도하면 다음과 같은 결과가 출력됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ dig google.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;;; communications error to 127.0.0.53#53: timed out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;;; communications error to 127.0.0.53#53: timed out&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해결 방안은 TLS 기반 DNS를 비활성화하면 됩니다. &lt;code&gt;resolved&lt;/code&gt; 설정 파일을 수정하여 이를 해제할 수
있지만, 모든 연결에 대해 비활성화되기 때문에 선호하는 방법은 아닙니다. 차라리 (&lt;code&gt;NetworkManager&lt;/code&gt;를
사용하고 있다는 전제하에) &lt;code&gt;NetworkManager&lt;/code&gt;에서 “Boingo Hotspot” SSID를 블랙리스트 처리하는
것이 낫습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ nmcli connection show&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ nmcli connection modify _Free_ORD_Wi-Fi connection.dns-over-tls 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;0&lt;/code&gt;은 비활성화, &lt;code&gt;1&lt;/code&gt;은 자동으로 활성화 시도, 그리고 &lt;code&gt;2&lt;/code&gt;는 활성화 옵션 값입니다.&lt;/p&gt;
&lt;p&gt;이제 &lt;code&gt;systemd-resolve --status&lt;/code&gt;를 통해 TLS 기반 DNS가 비활성화되었는지 확인할 수 있습니다.
정상적으로 값이 저장되었다면 다음과 같은 출력이 표시되어야 합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ systemd-resolve --status&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Global&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;           Protocols: +LLMNR +mDNS +DNSOverTLS DNSSEC=yes/unsupported&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Link 3 (wlan0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Current Scopes: DNS LLMNR/IPv4 LLMNR/IPv6 mDNS/IPv4 mDNS/IPv6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;         Protocols: +DefaultRoute +LLMNR +mDNS -DNSOverTLS DNSSEC=yes/supported&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Current DNS Server: 100.109.0.31&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       DNS Servers: 100.109.0.32 100.109.0.31&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     Default Route: yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;DNSOverTLS&lt;/code&gt; 앞에 &lt;code&gt;-&lt;/code&gt; 표시가 있다면 정상적으로 설정된 경우입니다.&lt;/p&gt;
&lt;p&gt;그렇게 해서 DNS 쿼리를 고쳤지만 아직도 인터넷 접속이 되지 않았습니다. 그리고 그 이유는 바로&lt;/p&gt;
&lt;h2 id=&quot;문제-2번--dns-가로채기는-https와-동작하지-않지요&quot;&gt;문제 2번 — DNS 가로채기는 HTTPS와 동작하지 않지요&lt;/h2&gt;
&lt;p&gt;2번은 조금 더 전반적인 팁인데, 알고 있는 인증 페이지 시스템 중에서 &lt;a href=&quot;https://google.com&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;구글&lt;/a&gt; 등의
도메인을 가로채기 할 수 있는 시스템은 없는 것으로 알고 있습니다. 이 문제를 우회하기 위해, 거의 대부분의
OS와 브라우저 회사들은 연결 상태를 확인하기 위해 전용 HTTP 주소를 사용하는데, 가끔씩 이것도 실패할
때도 존재합니다. 그리고 HSTS와 같은 규격이 더 도입되면서, HTTP로 다운그레이드를 허용하는 사이트를
찾기 점점 더 어려워지고 있죠.&lt;/p&gt;
&lt;p&gt;따라서 가장 간단한 방법은 &lt;a href=&quot;https://neverssl.com&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;neverssl.com&lt;/a&gt;를 방문하는 것인데, 그러면
인증 페이지로 리디렉트되어 인증 후 인터넷을 사용할 수 있을 겁니다.&lt;/p&gt;
&lt;p&gt;왜 아직도 몇몇 장소에선 이렇게 불편하게 인증 후 인터넷을 사용할 수 있도록 시스템을 짜놓는지 모르겠습니다.
광고 노출하면서 광고비를 챙겨 먹으려 하는 것 같은데, 사용자 입장에선 불편할 수밖에 없거든요.&lt;/p&gt;</content:encoded></item><item><title>컨트롤러가 작동하지 않으면, 키보드를 의심해라</title><link>https://ericswpark.com/ko/blog/2025/2025-12-11-linux-controller/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2025/2025-12-11-linux-controller/</guid><pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;시험 기간에 게임 회사들하고 인디 개발진이 게임(아니면 확장팩)을 출시하는 걸 법으로 금지해야 한다고
봅니다. 제 리눅스 머신마저도 마지막 보루로써 제 성적을 구해보려고 했는지, 게임에 진입하면 컨트롤러가
작동하질 않았습니다.&lt;/p&gt;
&lt;p&gt;그러니까 가지고 있는 Xbox One 컨트롤러를 리눅스 노트북에 페어링과 연결도 할 수 있었고, Steam에서도
인식이 되었으며 Xbox 버튼을 누르면 “큰 화면 모드 (Big Picture Mode)“로 진입도 하였지만, 정작
게임에 진입하게 되면 조작에 아무런 반응이 안 나타나는 겁니다.&lt;/p&gt;
&lt;p&gt;만약 Steam의 컨트롤러 번역 레이어를 거치면 나아지지 않을까 생각되어 Steam Input을 활성화해봤지만,
그것도 작동하지 않았습니다. 리눅스 커널에서는 컨트롤러가 어떻게 매핑되는지 궁금해서 &lt;code&gt;/dev/input&lt;/code&gt;
안에 뒤적여보니, 안에서 &lt;code&gt;js0&lt;/code&gt;을 찾을 수 있었습니다. (줄여서 joystick-0이라고 하는군요.)&lt;/p&gt;
&lt;p&gt;그래서 &lt;code&gt;cat /dev/input/js0&lt;/code&gt;을 하면, 컨트롤러에 입력을 했을 때 콘솔에 깨진 문자열이 나와야
되겠죠? 하지만 아직도 출력이 나오질 않았습니다.&lt;/p&gt;
&lt;p&gt;그러다가 발견하게 된 &lt;code&gt;js1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;다른 기기 폴더와 같이, &lt;code&gt;/dev/input&lt;/code&gt;은 &lt;code&gt;by-id&lt;/code&gt;라는 폴더가 안에 있는데, 이전에 사용된 인식 순서
방식과 다르게 기기를 고유 식별 코드로 나열해 줍니다. 그리고 여기를 확인해 보니 역시:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ ls /dev/input/by-id/*joystick&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/dev/input/by-id/usb-Keychron_Keychron_K2_Pro-if02-event-joystick&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그래서 노트북에 연결된 키보드를 빼고 다시 게임을 실행해 보니, 컨트롤러가 제대로 인식되기 시작했습니다.&lt;/p&gt;
&lt;p&gt;이제 다음 주 금요일까지만 버틸 수 있을지.&lt;/p&gt;</content:encoded></item><item><title>NixOS를 더 이상 사용하지 않는 이유들</title><link>https://ericswpark.com/ko/blog/2025/2025-09-20-why-im-moving-away-from-nixos/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2025/2025-09-20-why-im-moving-away-from-nixos/</guid><pubDate>Sat, 20 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;거의 1년 동안 NixOS를 시험해보면서,
&lt;a href=&quot;https://github.com/ericswpark/nixos-config&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;사용하는 기기들의 설정값들을 한 레포지토리&lt;/a&gt;에 적어두었습니다.
NixOS를 돌리는 모든 기기들에 프로그램을 새로 설치할 때마다, 설정값을 바꿀 때마다, 커밋으로
작성하여 레포지토리에 올려두었죠.&lt;/p&gt;
&lt;p&gt;거의 1년 간 사용을 마무리지으며, 배운 장점과 단점들을 나열해보고, 왜 결국엔 NixOS를 더 이상
사용하지 않을지 적어보겠습니다.&lt;/p&gt;
&lt;h2 id=&quot;주의&quot;&gt;주의&lt;/h2&gt;
&lt;p&gt;이런 글에는 집단주의가 언제나 따라오기 마련이니 항상 적어두는 주의사항입니다.&lt;/p&gt;
&lt;p&gt;이 블로그 글은 왜 &lt;em&gt;제가&lt;/em&gt; NixOS를 더 이상 사용하지 않는지 서술합니다. 아래 나열하는 장단점은
본인에게 적용되지 않을 수 있습니다. 어떤 것들은 쉬운 해결책을 찾으실 수도 있습니다. 어떤
것들은 말도 안 된다고 생각하실 수 있죠.&lt;/p&gt;
&lt;p&gt;아래 댓글을 작성하신다면 다른 분들에게 도움이 되기를 바라면서 작성하시기 바랍니다.
(그리고 만약 블로그 글에 관련성이 높다면 글을 수정하여 댓글을 참조하겠습니다.) 하지만 댓글에
본인 입장이 가장 올바르고 좋은 의견이라고 작성하시지 마시기 바랍니다. 거의 대부분 모든 분들께
적용되는 입장이 아닐 가능성이 높으니까요. 감사합니다.&lt;/p&gt;
&lt;h2 id=&quot;장점들&quot;&gt;장점들&lt;/h2&gt;
&lt;p&gt;일단 장점부터 나열하고 싶은데, NixOS를 사용하면서 몇 가지 점들은 진짜 사용성을 편리하게
해주었습니다.&lt;/p&gt;
&lt;h3 id=&quot;초기-복구-시간-단축&quot;&gt;초기 복구 시간 단축&lt;/h3&gt;
&lt;p&gt;만약 노트북이 고장나거나, SSD가 죽거나, 엄청난 실수를 저질러 작업공간을 망친다면, 일반적인
재설치 절차는 설치 미디어를 생성하고, 드라이브를 지우며, OS를 재설치하고, 드라이버를 다시 깔고
(macOS도 당연히 해당됩니다 — 모든 드라이버가 포함되어 있지 않죠), 프로그램들을 모조리 다시
설치하고, 하나하나 설정해야 했습니다.&lt;/p&gt;
&lt;p&gt;NixOS도 비슷하지만, 운영체제 설치 후에는 이야기가 조금 달라집니다. 상기 서술한 절차와 다르게,
그냥 레포지토리를 복사하고, “symlink”를 만든 다음 &lt;code&gt;sudo nixos-rebuild switch&lt;/code&gt;를
돌리면 컴퓨터가 거의 대부분&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; 복원됩니다. 설치했던 프로그램들을 전부 포함해서 말이죠!&lt;/p&gt;
&lt;p&gt;물론, 만약 프로그램들이 NixOS를 통한 설정을 지원하지 않는다면 수동으로 설정해야 되지만,
무슨 프로그램들이 깔려 있었는지 외운 다음에 다시 다 설치하지 않아도 되어 전반적으로 시간을
많이 절약해줍니다.&lt;/p&gt;
&lt;h3 id=&quot;잘못된-설정값들에-대한-빠른-복구&quot;&gt;잘못된 설정값들에 대한 빠른 복구&lt;/h3&gt;
&lt;p&gt;만약 설정을 잘못 만지거나, 원하지 않는 프로그램을 설치한다면, 복구는 그냥 &lt;code&gt;git revert&lt;/code&gt; 후
&lt;code&gt;sudo nixos-rebuild switch&lt;/code&gt;로 해버릴 수 있습니다.&lt;/p&gt;
&lt;p&gt;게다가 컴퓨터에 문제가 생기면 무슨 변경사항이 문제를 일으켰는지 &lt;code&gt;git bisect&lt;/code&gt;로 확인할 수
있습니다. 어떤 결정사항이 컴퓨터에 문제를 일으켰는지 이분법으로 찾는 상상을 하셨다면,
NixOS에선 그게 가능합니다.&lt;/p&gt;
&lt;p&gt;그리고 각 설정값 변경이 Git 커밋 메시지로 서술되니, 6개월 후에 왜 이런 이상한 결정을 했는지
기억 깊숙한 곳에서 끄집어내지 않아도 되죠.&lt;/p&gt;
&lt;h2 id=&quot;단점들&quot;&gt;단점들&lt;/h2&gt;
&lt;p&gt;장점은 2개밖에 안 적고 바로 단점으로 들어간다고요? (아니, 블로그 제목을 보면 맞긴 하지만…)&lt;/p&gt;
&lt;h3 id=&quot;프로그램-설치할-때의-불편함&quot;&gt;프로그램 설치할 때의 불편함&lt;/h3&gt;
&lt;p&gt;다른 운영체제들에 어플을 설치하려면 &lt;code&gt;.exe&lt;/code&gt; 파일을 더블클릭하여 프롬프트들을 답해 나가거나,
&lt;code&gt;.dmg&lt;/code&gt; 안 앱을 폴더로 드래그하거나, 아니면 &lt;code&gt;sudo pacman -S blender&lt;/code&gt;를 입력하면 됩니다.
NixOS에서는 프로그램 설치를 할 때마다 설정값 레포지토리를 열고, 현재 사용중인 호스트의
Nix 설정 파일을 찾은 후, 설치하고 싶은 프로그램에 대해 줄을 추가해야 합니다.&lt;/p&gt;
&lt;p&gt;사실은요. 전체 절차는 먼저 &lt;a href=&quot;https://search.nixos.org&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;search.nixos.org&lt;/a&gt;에 접속하여,
설치하고 싶은 패키지의 패키지명을 검색하고, 설정 레포지토리를 연 다음, 현재 사용중인 호스트의
Nix 설정 파일을 찾은 후, 설치하고 싶은 프로그램에 대해 줄을 추가해야 합니다.&lt;/p&gt;
&lt;p&gt;(물론 어떤 NixOS 전문가들이 &lt;code&gt;nix-locate&lt;/code&gt;와 같은 명령들이 있다는 점을 얘기하겠지만,
제가 지난 몇 달간 테스트해본 결과 이 방법도 완벽하지는 않습니다. 예를 들어 &lt;code&gt;dnsutils&lt;/code&gt; 패키지
안 &lt;code&gt;dig&lt;/code&gt; 도구를 찾을 때 &lt;code&gt;nix-locate&lt;/code&gt;로는 찾을 수 없었지만 실제 사이트에서는 찾을 수 있었습니다.)&lt;/p&gt;
&lt;p&gt;그리고 이건 &lt;code&gt;nixpkgs&lt;/code&gt;에서 추적되고 있는 프로그램에 대해서만 해당되죠. 하지만 추적되지
않는 프로그램들은 어떻게 될까요?&lt;/p&gt;
&lt;h3 id=&quot;nixos-사용-nix를-배우는-전제-조건&quot;&gt;NixOS 사용, Nix를 배우는 전제 조건&lt;/h3&gt;
&lt;p&gt;“전 NixOS를 사용하면서 Nix를 배울 필요가 없었는데요?”&lt;/p&gt;
&lt;p&gt;아마도 그랬을 수도 있습니다. 하지만 이런 문제를 겪고 나면, 차우리 배워야 되겠다는 생각을 하실
수도 있죠.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nixpkgs&lt;/code&gt; 안에 제공되는 패키지들만을 사용한다면, NixOS를 사용하면서 크게 불편하지는 않을 겁니다.
하지만 제공되지 않는 패키지를 사용한다면 거기서부터 문제가 시작되죠.&lt;/p&gt;
&lt;p&gt;제 경우에는, 리눅스에서 한국어 입력 방식으로서 애용중인 &lt;code&gt;nimf&lt;/code&gt;라는 패키지와, 제 ThinkPad
T480의 지문인식 센서를 돌려주는 &lt;code&gt;python-validity&lt;/code&gt;라는 패키지가 둘 다 &lt;code&gt;nixpkgs&lt;/code&gt;에서
제공되지 않고 있습니다. &lt;code&gt;nixpkgs&lt;/code&gt;에 추가를 요청하는 요청서로 올라가 있긴 했지만 (두 번째
요청서는 제가 직접 작성했죠):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/NixOS/nixpkgs/issues/207116&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://github.com/NixOS/nixpkgs/issues/207116&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/NixOS/nixpkgs/issues/357359&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://github.com/NixOS/nixpkgs/issues/357359&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;NixOS 개발진이 패키지 요청서를 더 이상 받지 않기로 하면서 닫혀버렸습니다&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. 이제 이러한
패키지들을 원한다면, &lt;strong&gt;직접 기여해야 합니다&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;자동으로 닫는 봇이 작성한 메시지&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;939&quot; height=&quot;319&quot; src=&quot;/_astro/nixpkgs-package-yourself.qeEtXozg_IqTpd.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you wish to see this package in Nixpkgs, &lt;strong&gt;we encourage you to contribute
it yourself&lt;/strong&gt;, via a Pull Request.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(한국어 번역):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;만약 이 패키지를 Nixpkgs에서 만나보고 싶으시다면, &lt;strong&gt;저희는&lt;/strong&gt; Pull Request를 통해 &lt;strong&gt;이
패키지를 직접 기여하실 것을 권장합니다&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;그래서 만약 Nix를 모른다면, NixOS의 모든 것이 Nix를 기반으로 하고, 이러한 기여나 다른 작업을
할 수 없기에, 사용하고 싶은 도구를 사용하는데 시간을 할애하지 않고 Nix를 배우는 데 쏟아야 되죠.&lt;/p&gt;
&lt;p&gt;“이건 모든 배포판에 해당되지 않나요?”라고 하실 수도 있습니다. 그럼요. Arch에서는 프로그램을
패키징하기 위해 &lt;code&gt;PKGBUILD&lt;/code&gt; manifest를 작성해야 하겠죠. 하지만 완전히 새로운 언어를 배울
필요는 없을 겁니다. (제가 든 &lt;code&gt;PKGBUILD&lt;/code&gt; 예시는 &lt;code&gt;bash&lt;/code&gt;만 알면 작성할 수 있고,
&lt;a href=&quot;https://wiki.archlinux.org/title/PKGBUILD&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;절차도 Arch 위키에 매우 친절하게 서술되어 있죠&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;그럼 Arch의 &lt;code&gt;PKGBUILD&lt;/code&gt;를 배우고 싶지 않다고 가정해보겠습니다. 그러면 아직도 다른 수단이 준비되어
있죠. 바로 Arch User Repository(AUR)에 커뮤니티의 다른 사용자들이 기여한 &lt;code&gt;PKGBUILD&lt;/code&gt;들을
사용하면 됩니다.&lt;/p&gt;
&lt;p&gt;“하지만 사용자들이 기여한 &lt;code&gt;PKGBUILD&lt;/code&gt;에 악성코드가 숨어있을 수도 있잖아요!” 그러면 다른 방법을 더
찾아보겠습니다. (여기에서 &lt;code&gt;nixpkgs&lt;/code&gt;와 여기에 기여하는 모든 분들이 완전히 믿을만한 존재로 여겨진다는
부분은 무시하겠습니다.) 다른 배포판에서는 아직도 추가 수단이 준비되어 있는데, 바로 패키징 도구를
사용하지 않고 곧바로 패키지 자체를 컴파일한 후 설치를 할 수 있습니다. NixOS에선 이게 불가능한데,
바로 시스템 파일을 수정할 수 없도록 읽기 전용으로 처리되어 있기 때문이죠. 만약 강제로 파일을 바꾸더라도,
다음 시스템 세대 빌드를 진행해버리면 수정사항들이 전부 사라지게 됩니다.&lt;/p&gt;
&lt;p&gt;NixOS가 Nix를 기반으로 만들어졌으면 안된다는 말이 아닙니다. 말이 안되는 부분이죠. 배포판 이름에도
포함되어 있는데요. 하지만 이 방식은 운영체제를 돌리는 언어를 모르는 새로운 사용자들이 빠르게 적응하여
새로운 프로그램을 설치하는데 도움이 되질 않습니다.&lt;/p&gt;
&lt;p&gt;“당연히 NixOS를 사용하려면 Nix를 배워야죠! 일본에 가서 한국어로 대화하길 바라나요?”
하지만 일본어를 배우지 않고도 일본에서 불편하게라도 생활은 가능합니다. 다른 배포판에서는
이러한 &lt;code&gt;PKGBUILD&lt;/code&gt; 시스템을 배우고 싶지 않으면서도 필요한 도구를 사용할 수 있도록 대응책이
마련되어 있지만, NixOS와 Nix에서는 불변성을 중요시하기 때문에 이러한 불편사항을 조금이라도
완화해줄 대응책들이 막혀버리게 됩니다. 어떤 분들은 이것이 NixOS의 장점, 또는 기능이라고
여기실 수 있지만, 결국에는 이러한 사항들을 모른다면 불편한 점이 존재하는 부분은 동의하실만하다고
생각합니다.&lt;/p&gt;
&lt;h3 id=&quot;프로그램을-업그레이드할-때의-불편함&quot;&gt;프로그램을 업그레이드할 때의 불편함&lt;/h3&gt;
&lt;p&gt;예시를 들어, 만약 버전 3.6.12를 필요로 하는 파일을 받았다고 가정해보겠습니다. 하지만 컴퓨터에
깔려있는 버전은 3.6.6입니다. 어떻게 진행할까요?&lt;/p&gt;
&lt;p&gt;다른 운영체제에서는 3.6.12 버전의 설치 파일을 다운받아 설치한 후 바로 진행하면 됩니다. 아니면
&lt;code&gt;sudo pacman -S blender&lt;/code&gt; 아니면 &lt;code&gt;sudo apt upgrade blender&lt;/code&gt; 아니면 다른 명령만 실행하면
되죠. 요약하자면, 다른 운영체제에서는 이 부분이 쉽다는 점입니다.&lt;/p&gt;
&lt;p&gt;하지만 NixOS에서는 이것이 불가능합니다. 모든 패키지 메타데이터는 &lt;a href=&quot;https://github.com/NixOS/nixpkgs/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;nixpkgs&lt;/code&gt; 레포지토리&lt;/a&gt;
안에 저장되는데, 각 버전 업그레이드는 Git 커밋으로 기록됩니다. 아마도 이 시스템을 유지보수하는
NixOS 개발진에게는 좋은 시스템일지 모르겠지만, 이 방식으론 사용자의 Nix “flake” lockfile
(잠금파일, 버전이나 Git 커밋 해시를 특정 시점에 고정시켜버리는 파일)에 기록된 커밋 해시에
대응하는 프로그램 버전들만 설치된다는 문제가 있습니다. 따라서 3.6.12로 업그레이드를 원한다면
커밋 해시를 버전 3.6.12를 포함하는 해시로 변경해야 하죠.&lt;/p&gt;
&lt;p&gt;문제는, 최초에 기록되었던 커밋 해시와 새로운 3.6.12 커밋 사이에 있던 커밋들이 전부 포함되면서
해당 커밋들에 상응하는 패키지들도 모조리 업그레이드되는데, 한 패키지 업그레이드를 시도하다가
전부 다 업그레이드를 진행하면서 최소 20분 (너무 업그레이드를 안 했다면 몇 시간!)이 걸려
골치아픈 상황이 생길 수 있습니다.&lt;/p&gt;
&lt;p&gt;그리고 업그레이드가 완성되면, 마지막 &lt;code&gt;flake&lt;/code&gt; lock과 현재 lock 사이에 어떤 버전들이 변경되었는지
쉽게 확인할 방법이 없는 부분도 문제가 됩니다. 제 경우에는 컴퓨터를 재부팅한 후 바탕화면이 바뀌면
KDE가 업그레이드 되었다는 것을 알게 되고, 프로그램을 열 떄 “변경사항” 팝업이 나타나면 그제서야
알게 됩니다. NixOS는 업그레이드할 패키지 목록을 표시해주지 않으니까요.&lt;/p&gt;
&lt;h3 id=&quot;컴파일-속도&quot;&gt;컴파일 속도&lt;/h3&gt;
&lt;p&gt;그러면 업그레이드 절차가 왜 이렇게 오래 걸릴까요?&lt;/p&gt;
&lt;p&gt;시스템 업그레이드를 진행하면서 콘솔을 확인해보면 거의 대부분 패키지들이 실행할 수 있는 바이너리
형식으로 컴파일되는 부분을 확인하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;하지만 NixOS는 &lt;a href=&quot;https://cache.nixos.org&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;cache.nixos.org&lt;/a&gt;에 바이너리 캐시가 있는데요? 빌드가 그렇게
오래 걸릴 리 없어요. 뭐가 문제죠?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;서브도메인의 이름에서 유추할 수 있듯이, 패키지들이 먼저 NixOS의 서버에서 컴파일되어야 베포를
위해 서버에 캐싱됩니다. 만약 요청한 패키지가 예전에 요청된 적 없는 패키지거나, 아니면 기존
패키지에 새로운 업데이트가 추가된다면, 캐시된 바이너리를 받을 수 있는 상태가 되려면 빌드 작업이
진행되어야 합니다.&lt;/p&gt;
&lt;p&gt;그리고 만약 설정값 안에 빌드 절차 수정사항이 들어 있다면 다른 바이너리를 요구로 하는데, 이런
경우에는 NixOS 서버에 존재할 이유가 거의 없죠. 그러면 NixOS는 곧바로 패키지를 시스템 상에서
소스를 사용해 컴파일하는 방식으로 전환하게 됩니다.&lt;/p&gt;
&lt;p&gt;물론 노트북을 매번 &lt;code&gt;switch&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;#user-content-fn-3&quot; id=&quot;user-content-fnref-3&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;를 돌릴 때마다 전기장판으로 변신시키지 않도록 빌드 캐시 서버를
직접 구성하실 수 있지만, 이 역시 설정을 처음부터 다 하고 문제가 없는지 항상 확인해야 되는
번거로움이 생기게 됩니다.&lt;/p&gt;
&lt;h3 id=&quot;nixos를-통한-전체-설정&quot;&gt;NixOS를 통한 “전체” 설정&lt;/h3&gt;
&lt;p&gt;NixOS 설정에 모든 설정값들을 작성하고 설명하는 부분은 어느 정도까지는 매우 좋습니다. 이
“어느 정도”가 끝나는 부분은 바로 Nix 설정줄로 변환되지 않은 설정값과 맞닥뜨리는 경우죠.&lt;/p&gt;
&lt;p&gt;예를 들어, 애용하는 생산성 프로그램에 있는 어떤 숨겨진 체크박스가 설정값을 변경하는데 이 설정
변경이 파일에 기록되지 않는다고 가정해보겠습니다. 아니면 어떤 클라우드 방식 어플이 호스트네임을
기반으로 설정을 클라우드에 저장한다고 생각해보고요. 조금 억지스러운 예시일 수 있지만, 제가
말씀드리고자 하는 부분은 “전부 다” NixOS에서 설정을 할 수 있다는 부분이 아니라는 점입니다.&lt;/p&gt;
&lt;p&gt;그리고 어쩔 땐 크게 문제가 되지는 않습니다. 예시로 제가 사용한 데스크탑 환경인 KDE에서는
각 시스템마다 설정하고 싶은 옵션값들이 존재합니다. 하지만 만약 시스템 재설치 시 설정이 지속될
수 있도록, 매번 옵션을 변경하거나 체크박스를 수정할 때마다 설정 파일을 변경해야 한다면, 아마도
빠르게 미치지 않을까 싶습니다.&lt;/p&gt;
&lt;p&gt;물론, KDE 예시에서는 현재 설정값을 Nix 설정으로 변환해주는 &lt;code&gt;rc2nix&lt;/code&gt;와 같은 도구가 존재하기는
합니다. 하지만 제가 사용해본 경험으론 완벽하지 않은데, KDE와 같이 오는 어플들은 설정하는 방식이
제각각이고 다 다른 곳에 설정 파일을 저장하기 때문입니다. (아니, 파일 매니저인 Dolphin만
보더라도 &lt;code&gt;.dolphinrc&lt;/code&gt;에 설정을 저장하는데, &lt;code&gt;rc2nix&lt;/code&gt;는 이를 확인하지 않기 때문에, 이 파일이
어디에 저장되어 있고 어떤 줄들이 현재 호스트에 영향을 끼치며 어떻게 파일을 Nix 설정 레포지토리에서
symlink해 올 수 있는지 확인해야 되고 Dolphin이 만약 새로운 줄들을 추가한다면 기존 설정 파일에
제대로 추가되는지 확인해야 되며 만약 리빌드할 경우 설정 파일 안 변경사항들이 지워지지 않는지
확인해야 되고 이런 것들을 계속 반복하다 보면 머리가 아파오기 마련이죠.)&lt;/p&gt;
&lt;p&gt;Dolphin 예시처럼, 모든 프로그램들이 외부적으로 설정되는 것을 달가워하지 않습니다. 프로그램들은
대부분 어디에서나 아무 운영체제에서나 (그리고 리눅스에서는 아무 베포판에서나) 실행되도록 짜이기
때문에, 거의 대부분 NixOS의 존재조차 모를 가능성이 높습니다. 따라서 NixOS가 운영하는 방식을
알지 못해 동일하게 맞춰 나가지 않는 부분이 많죠.&lt;/p&gt;
&lt;p&gt;그리고 만약 설정 변경이 설정 파일을 복사하거나 symlink하는 것을 요구한다면, NixOS가 리빌딩을
진행할 경우 프로그램에서 만든 수정사항들이 전부 날라가버리면서 사용자 경험을 망치게 됩니다.
NixOS에서 (설정할 수 있는) “모든 것”을 설정하는 것을 원칙으로 하지만, 아까 보셨던 것처럼
NixOS를 통해 “모든 것”을 설정하는 것은 불가능하기 때문에, 이렇게 2개 이상의 설정 원천이
서로 대립하면서 설정값들이 삭제되고 나쁜 시간이 펼쳐지죠.&lt;/p&gt;
&lt;h1 id=&quot;마치면서&quot;&gt;마치면서&lt;/h1&gt;
&lt;p&gt;그러면 NixOS가 엄청 나쁘고 아무도 쓰지 않아야 되는 베포판인가요? 아니요. 위에 적은 단점들을
겪음에도 불구하고 NixOS를 사용하고 배우는 시간이 재밌었습니다. 하지만 저는 시스템을 사용할 때
저만의 방식을 고집하고, 시간이 지나면서 이러한 컴퓨팅 방식이 NixOS가 추구하는 선언적 및 불변적
방식과는 다르다는 부분을 인지하게 되었습니다.&lt;/p&gt;
&lt;p&gt;아마도 나중에 시간이 나서 Nix를 완전하게 배울 수 있고, 위에서 겪었던 단점들을 어느 정도 완화할
수 있도록 자료와 문서가 나아진다면 다시 NixOS를 시도해볼 의향이 있습니다. 하지만 지금은 제가
원하는 일을 할 수 있도록 컴퓨터를 구성하고 싶고, NixOS의 단점들이 기능을 통해 제공하는 장점보다
더욱 많아 업무에 지장을 주게 되어 한번 글을 작성해보았습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;여기에서 “거의 대부분”이라고 말하는 이유는 하단의 단점 부분에서 더 서술하겠습니다. &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;물론 당연히 개발진이 안 받겠다고 하는데 이게 잘못되었다는 건 아닙니다! 그래도 사용자들이
사용하기에 불편해지는 건 다름없으니, 제 단점이 틀리게 되는 것도 아니죠. &lt;a href=&quot;#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-3&quot;&gt;
&lt;p&gt;이 명령구는 엄청 쓰기 편리한데 왜 기본이 아닌지 모르겠습니다.
&lt;a href=&quot;https://github.com/ericswpark/nixos-config/blob/master/home-manager/common/nixos-aliases.nix&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;원하시면 이렇게 설정해보시면 됩니다.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-3&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>Bluu 인턴십에서 나온 작은 프로젝트들</title><link>https://ericswpark.com/ko/blog/2025/2025-08-29-bits-and-pieces-from-my-bluu-internship/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2025/2025-08-29-bits-and-pieces-from-my-bluu-internship/</guid><pubDate>Fri, 29 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;인턴십 기간동안 무엇을 했는지 글을 작성하려고 했었는데, 오늘까지 할 기회가 없었습니다.
2025년 여름방학 기간 중 만든, 공유를 수락받은 두 프로젝트를 나열해보겠습니다:&lt;/p&gt;
&lt;h1 id=&quot;보고서-도구&quot;&gt;보고서 도구&lt;/h1&gt;
&lt;p&gt;많은 회사들은 직원들이 하루에 무슨 일을 했는지 시간 추적 및 관리하는 시스템을 갖추고 있습니다.
제 경우에는 개인 리포트를 작성한 후, 다른 팀원들과 합치고 병합된 리포트를 팀 전체 리포트로
전송해야 됐죠. 문제는, 한 테이블로 병합하는 과정이 꽤 까다로웠고, 만약 누군가가 이미 만들어진
테이블을 수정해야 했다면 테이블 레이아웃이 바뀌어 문제가 많이 생겨버렸습니다.&lt;/p&gt;
&lt;p&gt;따라서 사용자들이 하루 일과 아이템을 바로 입력하고 개인 리포트로 출력, 그리고 한꺼번에 한
테이블로 병합해주는 웹앱을 만들어봤습니다. 이름과 프로젝트명의 계급/중요도 순서를 사용하여
자동으로 테이블을 정렬해주는 편의 기능들도 넣었습니다. (숨겨진 이스터 에그도 있죠!)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ericswpark.com/bluu_daily-event-report-tool/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;여기에서 웹앱을 확인하실 수 있습니다&lt;/a&gt;. 그리고 &lt;a href=&quot;https://github.com/ericswpark/bluu_daily-event-report-tool&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;소스 코드는 여기에서
확인하실 수 있습니다&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;소스 코드를 보시기 전에 한 가지 설명할 부분이 있습니다. &lt;code&gt;README.md&lt;/code&gt; 파일에서도 언급했지만,
페이지는 그냥 한 HTML 파일 안에 인라인 (inline) Babel과 &lt;code&gt;&amp;#x3C;script&gt;&lt;/code&gt; 태그들을 넣어
만들었습니다. 이 웹앱을 작성할 때 직장 컴퓨터에는 개발 도구가 설치되어 있지 않았고, 개인
컴퓨터에서 작성해서 넘기는 것도 골치아픈 부분이 많았을 것이기에 이렇게 진행하게 되었습니다.
그래서 한 파일 안에 모든 게 작성되어 있지만, 우려했던 것만큼 편집하기 어렵지 않았습니다.
VS Code 같은 최신 텍스트 편집기/IDE로 수정했는데, 얘는 공교롭게도 Microsoft Store 어플에서
바로 다운받을 수 있었습니다.&lt;/p&gt;
&lt;h1 id=&quot;자동-출퇴근-스크립트&quot;&gt;자동 출퇴근 스크립트&lt;/h1&gt;
&lt;p&gt;ADP는 급여/인사 플랫폼으로, 회사들이 직원들의 출퇴근 기록을 추적하는데 많이 사용합니다.
ADP의 한 가지 문제점은 (아니면 브라우저의 문제랄까요?) Microsoft Edge에서 비밀번호 자동완성
기능이 동작하지 않는다는 점입니다. 그래서 매일 출근할 때마다, 자리까지 뛰어가서 윈도우가
절전모드에서 나오길 기다린 다음, ADP 페이지가 완전히 로딩될 때까지 기다리고, (짐승처럼)
비밀번호를 일일히 입력하고, 메인 페이지가 로딩될때까지 기다린 다음, 마침내 “출근” 버튼을
클릭해야 했습니다.&lt;/p&gt;
&lt;p&gt;그래서 일반적인 일반인이 할만한, 다른 크로미움-기반의 브라우저로 변경하기보다, ADP 출근을 자동으로
진행해줄 Python 스크립트를 작성했습니다. 이제 출근하면 바로가기 하나만 클릭한 다음 가서 커피를
내려오면, 다시 자리에 올때쯤 출근 처리가 완료되어 있었죠.&lt;/p&gt;
&lt;p&gt;경고 1: ADP가 만약 웹사이트 디자인을 변경할 경우 이 스크립트가 부러질 수 있습니다. (아니,
거의 당연히 부러질 수 밖에 없겠죠.)&lt;/p&gt;
&lt;p&gt;경고 2: 타이밍에 의존하는 스크립트이다 보니 가끔씩 불안정하게 작동하는 경우가 있는데, 커피를
타온 다음에 제대로 출근 처리가 되었는지 확인이 필수입니다!&lt;/p&gt;
&lt;p&gt;경고 3: 출근한 다음에 수동으로 스크립트를 돌리는 것은 괜찮지만, 만약 어떤 스케줄러에다가
연결해둔 다음에 출근하지도 않고 자동으로 출근처리를 돌려버리면 불법일 수 있습니다. 전 변호사가
아니니, 법률 상담은 직접 구하세요.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/ericswpark/d405fa6fe50885e7b2bb3b2360542316&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;스크립트 전체를 올려둔 Gist 링크입니다&lt;/a&gt;. 로그인 정보를 제공해야 되고,
사용하고 있는 브라우저에 맞는 WebDriver 경로를 수정해야 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;그걸로 이 글을 마치겠습니다! 이번 여름에 만든, 공유를 허락받은 조그만 잡동사니로 생각해주시면
됩니다.&lt;/p&gt;</content:encoded></item><item><title>저렴한 IP 카메라 뜯어보다가 짜증나서 공유하는, 헤이홈 카메라 분해기</title><link>https://ericswpark.com/ko/blog/2025/2025-04-18-a-rant-about-cheap-proprietary-ip-cameras-featuring-hej-home/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2025/2025-04-18-a-rant-about-cheap-proprietary-ip-cameras-featuring-hej-home/</guid><pubDate>Fri, 18 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;몇달전에, 친척집 정리를 돕다가 옷장에서 뜯어보지도 않은 실내용 IP 카메라를 한 대 찾게 되었습니다. 헤이홈이란 중소기업에서 나온 제품이었는데, 필요하지도 않고 원하지도 않는다고 하셔서 한번 어떻게 작동하는지, 그리고 로컬-전용 IP 카메라로 설정할 수 있는지 알아보면 재밌겠다 생각했습니다.&lt;/p&gt;
&lt;p&gt;너무 기대하시기 전에, 성공담은 아니라는 점 알고 계시면 되겠습니다. 이 글에선 카메라가 원하는 방식으로 동작하도록 씨름한 경험담과, 로컬 연결 옵션이 없는 이런 저렴이 IoT 제품들이 폐가전이 되어가는 짜증나는 현실에 대해 한번 적어보려고 합니다.&lt;/p&gt;
&lt;h1 id=&quot;사양&quot;&gt;사양&lt;/h1&gt;
&lt;p&gt;일단 제가 가지고 있는 제품은 “스마트 홈카메라 프로”라는 이름으로 출시되었는데, 제품 뒤에 적힌 공식 모델 식별명은 &lt;code&gt;GKW-MC055&lt;/code&gt; 되겠습니다. 전원 공급선만 연결되어 있는 마이크로USB 단자가 하나 뒤에 자리잡고 있고, 리셋 구멍과 마이크로SD 카드 슬롯 하나가 전부입니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;카메라 본체&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2020&quot; height=&quot;3290&quot; src=&quot;/_astro/main-body.DOFltYBa_esVsb.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;앞에는 조그만 렌즈가 하나 있고, 위에 두 개의 LED 표시등이 있습니다. 하나는 카메라의 상태를 나타내는데, 전원이 연결되면 빨간색으로 빛나다가 부팅이 완료되면 파란색으로 바뀝니다. 다른 LED는 카메라가 야간 적외선 모드로 진입하면 켜지는데, 이때 렌즈 주변부 링에 자리잡고 있는 IR LED 등들이 전부 켜지면서 주변 환경을 비춰줍니다.&lt;/p&gt;
&lt;p&gt;추가적으로 어플로 양방향 소통이 가능토록 스피커와 마이크도 각각 하나씩 구비되어 있습니다.&lt;/p&gt;
&lt;p&gt;어플 얘기가 나왔으니 한번 설정 과정을 진행해보겠습니다!&lt;/p&gt;
&lt;h1 id=&quot;공식-어플에서-카메라-설정하기&quot;&gt;공식 어플에서 카메라 설정하기&lt;/h1&gt;
&lt;p&gt;일단 공식 제조사 어플로 카메라를 설정해보는게 가장 보편적인 방법이겠죠. 하지만 다운로드하고 열어보니 계정을 만들라고 닦달합니다. &lt;em&gt;귀찮지만…&lt;/em&gt; 그냥 시키는대로 계정을 하나 파서 카메라 스트림에 모습이 나오는 것까지 확인해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;어플 스크린샷&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1440&quot; height=&quot;3120&quot; src=&quot;/_astro/app-screenshot.CT2UGtiN_Z1jiPfb.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;다음으로 설정 메뉴에 진입해서 RTSP나 ONVIF로 로컬 접속을 가능하게 하는 옵션이 있는지 찾아봤지만 어떠한 설정값 하나 없었습니다. FAQ 섹션에도 관련 정보는 하나도 찾아볼 수 없었고요.&lt;/p&gt;
&lt;h1 id=&quot;ip-카메라-포트-스캐닝하기&quot;&gt;IP 카메라 포트-스캐닝하기&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;아마도 로컬 접속 포트는 이미 열려있지 않을까?&lt;/em&gt; 생각해봤습니다.&lt;/p&gt;
&lt;p&gt;카메라 포트 스캔을 할 시간이 왔군요!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ nmap 192.168.8.123&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-17 13:33 US Eastern Daylight Time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Nmap scan report for xxx.lan (192.168.8.123)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Host is up (0.022s latency).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Not shown: 999 closed tcp ports (reset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;PORT     STATE SERVICE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;6668/tcp open  irc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;MAC Address: B4:FB:E3:XX:XX:XX (AltoBeam (China))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Nmap done: 1 IP address (1 host up) scanned in 2.28 seconds&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이상하게도 포트 하나만 열려 있고, 이마저도 카메라에서 IRC 서버를 돌릴 이유가 없죠. 더 자세한 스캔을 시도해봐도 결과는 마찬가지였습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ nmap -sS -p- 192.168.8.123&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-17 13:35 US Eastern Daylight Time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Nmap scan report for xxx.lan (192.168.8.123)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Host is up (0.019s latency).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Not shown: 65534 closed tcp ports (reset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;PORT     STATE SERVICE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;6668/tcp open  irc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;MAC Address: B4:FB:E3:XX:XX:XX (AltoBeam (China))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Nmap done: 1 IP address (1 host up) scanned in 28.72 seconds&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;온라인에 검색해보니, 포트 &lt;code&gt;6668&lt;/code&gt;은 Tuya SDK를 기반으로 하는 IoT 기기들이 사용하는 것으로 확인했습니다. Tuya는 IoT 플랫폼을 다른 회사들이 사용할 수 있도록 라이센싱하고 베포하는 회사인데, 헤이홈이 거래하는 기업들 중 하나인 듯 했습니다. &lt;a href=&quot;https://hej.life/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;공식 판매처에서 그렇게 많은 제품들을 빠르게 쏟아낼 수 있는 이유&lt;/a&gt;이기도 하죠.&lt;/p&gt;
&lt;p&gt;하지만 이걸 안다고 도움이 되진 않죠. 카메라가 기반으로 하는 Tuya SDK에서 ONVIF를 활성화할 수 있는 방법이 있는지 찾아보다가 &lt;a href=&quot;https://developer.tuya.com/en/docs/iot-device-dev/tuyaos-package-ipc-device?id=Kcn1qb261j9je&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;이 페이지&lt;/a&gt;를 발견하게 되었습니다. ONVIF를 활성화할 수 있게 하는 설정값만 하나 바꾸면 되는 듯 해서 헤이홈을 운영하는 회사에다가 이메일을 보내, 이 설정값을 제발 활성화해주면 안되는지 문의했습니다. (찾기로는 이 값을 변경한다고 해서 Tuya에서 돈을 더 청구하는 건 아닌 걸로 알고 있는데, 바꾸는데 완전히 무료라면 안 해줄 이유가 없지 않을까요.) 3월 25일쯤 이메일을 보냈는데도, 아쉽게도 아직까지 답변을 받지 못했습니다.&lt;/p&gt;
&lt;p&gt;그럼 다음으론 무엇을 해볼 수 있을까요?&lt;/p&gt;
&lt;h1 id=&quot;카메라-분해하기&quot;&gt;카메라 분해하기&lt;/h1&gt;
&lt;p&gt;카메라를 분해해서 안에 있는 플래시 칩에서 펌웨어를 추출해, 정보를 더 캐낼 수 있는지 알아보기로 했습니다. 분해를 시작할 때 아랫 부분부터 확인하기 시작했는데, 카메라의 밑에 부분에선 마이크로USB 포트만 접근이 가능했습니다. 전원 케이블만 납땜해서 교체할 수 있게 되어 있어, 추후에 USB-C 포트로 교체할 수도 있게 되어 있었죠. 하지만 카메라의 주요 부품은 아랫쪽이 아니라 카메라의 윗부분에 자리잡고 있었는데, 만약 분해 과정에서 조금이라도 더 힘을 줘 강제로 분리했다면 카메라가 고장나서 더 이상 진행할 수 없었을 듯 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;윗부분 분해&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1167&quot; height=&quot;982&quot; src=&quot;/_astro/top-half-disassembly.vpd906PD_Z2kgF54.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;결국 이것저것 시도해보다 카메라의 윗부분을 분해할 수 있는 것을 알아냈습니다. 더 자세히 설명드리자면, 위에 있는 구 모양의 플라스틱 부분이 둘로 나뉘었는데, 여기에서 메인보드와 렌즈 구조물을 전부 꺼낼 수 있었습니다. 보드와 구조물은 얇게 접합되어 있었는데, 이 보드만으로 카메라의 모든 기능이 구현되어 있었습니다. 여기엔 수직/수평으로 카메라를 조절하는 모터들과, 녹화 기능을 위한 SD 카드 슬롯, 그리고 아랫 부분에서의 전원 단자에서 전원 공급까지 전부 다 포함되었죠.&lt;/p&gt;
&lt;h1 id=&quot;spi-플래시-칩-덤프하기&quot;&gt;SPI 플래시 칩 덤프하기&lt;/h1&gt;
&lt;p&gt;&lt;img alt=&quot;XMC사의 플래시 칩&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1080&quot; src=&quot;/_astro/flash-chip.CiZdQaFT_Z1Orgfb.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;바로 눈에 띤 부분은 XMC사 글귀와 칩 정보가 적혀있는 플래시 칩이였습니다. 마킹으론 &lt;code&gt;XMCQH64AHIG&lt;/code&gt; 모델명이 적혀 있었는데, &lt;code&gt;flashrom&lt;/code&gt;으로 내용물을 추출할 수 있을 거라고 알고 있었습니다. 하지만 &lt;a href=&quot;https://github.com/flashrom/flashrom/issues/148&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;flashrom GitHub를 조금 찾아보니 칩 지원에 대해 이 이슈를 발견했는데&lt;/a&gt;, 2020년 7월에 처음 열려 지금까지 해결되지 않은 문제였습니다.&lt;/p&gt;
&lt;p&gt;왜 연결된 두 패치가 닫혔는지 알아보다가, &lt;code&gt;flashrom&lt;/code&gt; 팀이 개발을 위해 GitHub를 사용하지 않는다는 점을 알아냈습니다. 개발은 전부 전용 Gerrit 서버에서 진행됐는데, 패치를 추가하려면 여기에서 리뷰를 받아야 했죠. GitHub에서 &lt;a href=&quot;https://github.com/flashrom/flashrom/pull/150&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;연결된 패치&lt;/a&gt;(그리고 &lt;a href=&quot;https://github.com/flashrom/flashrom/pull/239&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;또 다른 패치&lt;/a&gt;)들은 예전에 Gerrit으로 이전되었지만 무슨 이유에선지 없어졌고, 기존 패치들은 GitHub상에서 통합되지 않은 채 닫혀버렸습니다.&lt;/p&gt;
&lt;p&gt;그래서 한번 &lt;a href=&quot;https://review.coreboot.org/c/flashrom/+/86990&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;직접 패치를 접수해봤는데&lt;/a&gt;, 드디어 3월 29일에 정식으로 프로젝트에 포함되었습니다! 약 5년이 지나 &lt;code&gt;flashrom&lt;/code&gt;이 이 칩을 읽고 쓸 수 있게 된 셈이죠.&lt;/p&gt;
&lt;h1 id=&quot;펌웨어-덤프-확인하기&quot;&gt;펌웨어 덤프 확인하기&lt;/h1&gt;
&lt;p&gt;&lt;img alt=&quot;플래시 칩 읽기&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3000&quot; height=&quot;4000&quot; src=&quot;/_astro/flash-chip-reading.BWVa_fSO_ZBwHnn.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이제 칩 정보가 추가되었으니, 컴파일된 개발용 &lt;code&gt;flashrom&lt;/code&gt; 바이너리로 보드에서 펌웨어를 추출해냈습니다. 다음으로, &lt;code&gt;binwalk -Me cctv.bin&lt;/code&gt;을 사용해서 덤프 속 내용물을 추출한 다음 확인해봤습니다.&lt;/p&gt;
&lt;p&gt;폴더 중 이름이 &lt;code&gt;tuya_config.json&lt;/code&gt;인 파일 하나를 발견했는데, 안의 내용이 제가 찾던 것이랑 비슷해서 기대가 늘었습니다. 특히 이 옵션값이 중요해 보였습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;onvif_enable&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;그냥 이 값을 &lt;code&gt;1&lt;/code&gt;로 변경해버린다면?&lt;/em&gt; 갑자기 든 생각에, 펌웨어 덤프 파일을 복사한 후 &lt;code&gt;hexedit&lt;/code&gt;을 사용해서 연 후, 수동으로 값 &lt;code&gt;6F 6E 76 69 66 5F 65 6E 61 62 6C 65&lt;/code&gt;를 검색했습니다(16진수에서 변환하면 &lt;code&gt;onvif_enable&lt;/code&gt; 값이 됩니다). 바로 다음에 적혀있는 값 &lt;code&gt;30&lt;/code&gt; (&lt;code&gt;0&lt;/code&gt;)을 &lt;code&gt;31&lt;/code&gt; (&lt;code&gt;1&lt;/code&gt;)로 변경했습니다. 그 다음으로 수정된 펌웨어를 다시 기기에 입힌 후 전원에 연결해봤습니다.&lt;/p&gt;
&lt;p&gt;카메라가 부팅하면서, 예전에 없었던 놀랄만큼 큰 시작음이 들려왔습니다. 갑자기 난 소리에 놀랐지만 ONVIF가 드디어 활성화된 줄 알고 기대하고 있었죠. 아쉽게도, &lt;code&gt;nmap&lt;/code&gt;으로 재검색해본 결과, 추가로 열린 포트가 없다는 점을 확인했습니다.&lt;/p&gt;
&lt;p&gt;다음으로 헤이홈과 Tuya의 비공개 펌웨어를 갈아엎을, 완전히 새로운 펌웨어 이미지를 구할 수 있는지 찾아보기로 했습니다.&lt;/p&gt;
&lt;h1 id=&quot;오픈소스-옵션-검색해보기&quot;&gt;오픈소스 옵션 검색해보기&lt;/h1&gt;
&lt;p&gt;&lt;img alt=&quot;Anyka AK3918 SoC&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3804&quot; height=&quot;3416&quot; src=&quot;/_astro/anyka-soc.mUHiuE9O_1jk3zO.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;방금 전에 카메라를 분해하면서, 안에 사용된 SoC가 “Anyka”라는 회사에서 만들어진 &lt;code&gt;AK3918&lt;/code&gt; 칩이란 것을 확인했었습니다. 찾아보니, IoT 카메라 제조사들에게 납품하는, 통합형 SoC를 제조하는 중국 회사의 칩이였습니다.&lt;/p&gt;
&lt;p&gt;데이터시트가 공개되어 있었지만, 드라이버와 하드웨어 구성은 리눅스 커널에 공식으로 제출된 적이 없었죠. 중국 회사들은 이런 부분에서 특히 미흡한 것이 공통적입니다. 사실, 개조된 리눅스 커널을 제품과 함께 출시하였음에도 커널 소스조차 공개하지 않고 있는데, GPL에 위반되는 사항이지만 이러한 하드웨어 OEM들은 소프트웨어 라이선스 따위는 별로 신경쓰지 않는다는 걸 다 알고 계실 겁니다.&lt;/p&gt;
&lt;p&gt;이 SoC를 기반으로 한 카메라들을 역공학으로 분석하는 레포지터리가 몇몇 있었지만, 확인하면서 SoC가 버전별로 출시했다는걸 알게 됐습니다. 찾은 대부분의 레포지터리는 &lt;code&gt;AK3918v200&lt;/code&gt; 버전을 지원했는데, 제 카메라에 들어가 있는 버전은 역시 다른 &lt;code&gt;AK3918v300&lt;/code&gt; 버전이였죠. &lt;code&gt;v200&lt;/code&gt; 레포에서 수정을 해서 &lt;code&gt;v300&lt;/code&gt; 버전에 돌아가게 만든다 하더라도, 레포지터리 내용물은 간단한 스크립트들만 있었고, 이를 사용해서 RTSP나 ONVIF를 설정할 수 있을지도 의문이였죠.&lt;/p&gt;
&lt;h1 id=&quot;시간낭비-그만-요약-정리&quot;&gt;시간낭비 그만; 요약 정리&lt;/h1&gt;
&lt;p&gt;아쉽지만, 여기에서 마무리지어야 할 것 같습니다. 카메라를 분해하고, 안의 펌웨어를 추출하고, 그 과정에서 &lt;code&gt;flashrom&lt;/code&gt; 프로젝트에 기여하는 것도 재밌었지만, 더 이상의 노력은 시간낭비라고 여겨지니까요.&lt;/p&gt;
&lt;p&gt;비교 대상을 가져온다면, &lt;a href=&quot;https://www.amazon.com/Tapo-cameras-for-home-security/dp/B0CH45HPZT&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;ONVIF를 지원하는 카메라는 아마존에서 25불 정도&lt;/a&gt;에 구매할 수 있습니다. 구매하고, 설치하고, Home Assistant에 연결하는데 드는 시간은 방금 적어본 경험담보단 훨씬 덜 걸리죠. 이 카메라를 로컬-전용으로 연결할 수 있게 만드는 데 몇날며칠이 걸릴진 모르겠지만, 그 시간이 $25보단 더 값지다는 것은 확신할 수 있습니다.&lt;/p&gt;
&lt;p&gt;비교 대상을 잠시 잊더라도, 로컬 서버에 스트리밍이 원천적으로 가능한 카메라가 무슨 비공개 SDK 설정값 때문에 기능이 막혀있다는게 이해가 되질 않습니다. 이 기능이 없다면 저한테는 쓸모가 없고, 거의 대부분의 사용자들에게도 쓸모가 없겠죠. 집 내부를 중국 어딘가에 있는 서버 데이터센터에 스트리밍하면서 불안하지 않을 분들은 없을테니까요… 적어도 그래서는 안 되야겠죠.&lt;/p&gt;
&lt;p&gt;하지만 이렇게 구성된 플랫폼은 사용자들이 설정하기 쉬우니 Tuya와 기반한 회사들이 사용하는 방식인 듯 합니다. 폐가전을 운명으로 하는 이런 IoT 제품들을 계속 출시하면 전자제품에 별로 관심이 없는 분들이 구매하게 하는데, 포트 포워딩과 NVR 설치 등을 필요로 하지 않고 간단하게 사용할 수 있으니까요. 하지만 작동 방식에 대한 자세한 이해가 없다면, 제3자가 거실이나 아이 침실 등에 원격으로 접근할 수 있다는 가능성도 이해하기 어려울 겁니다.&lt;/p&gt;
&lt;p&gt;그래서 만약 지인분이 집에 실내 카메라(또는 아무런 IoT 기기)를 설치하려고 한다면, 로컬-전용 연결의 중요성을 강조하고, 사용하기 편리하더라도 인터넷을 경유하는 기기들을 들이지 않아야 하는 이유에 대해서 설명해주세요. 요즘에 Home Assistant는 설치하기도 쉽고, 몇 분 만에 설정 가능한 키트도 판매하고 있으니 차라리 이런 로컬 솔루션을 사용하지 않을 이유도 없죠.&lt;/p&gt;</content:encoded></item><item><title>Meta Quest 녹화본에서 오디오 딜레이 현상 수정하기</title><link>https://ericswpark.com/ko/blog/2025/2025-04-06-fix-up-audio-delay-in-meta-quest-recordings/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2025/2025-04-06-fix-up-audio-delay-in-meta-quest-recordings/</guid><pubDate>Sun, 06 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Quest 3을 2023년 10월에 구입했습니다. 오늘은 2025년 4월이죠. 거의 18개월 동안 펌웨어 업데이트를 꾸준히 출시했음에도 남아있는 버그가 하나 있는데, 그건 바로 Quest에서 게임 활동을 (“카메라” 어플로) 녹화한다면 오디오가 반 박자로 동기화가 풀려버린다는 것입니다.&lt;/p&gt;
&lt;p&gt;매번 둥영상을 편집할 때마다 편집툴로 딜레이를 고치는 작업이 귀찮아, &lt;code&gt;ffmpeg&lt;/code&gt;로 자동화할 수 있는지 알아봤습니다. (&lt;code&gt;ffmpeg&lt;/code&gt; 만능도구)&lt;/p&gt;
&lt;h1 id=&quot;첫째-딜레이-값-확인하기&quot;&gt;첫째: 딜레이 값 확인하기&lt;/h1&gt;
&lt;p&gt;일단 미디어 플레이어에 둥영상 클립을 로딩한 다음에, 오디오 딜레이 옵션을 활용해서 영상과 음성를 동기화시켜줬습니다. 예를 들어 비트세이버 (Beat Saber) 영상을 동기화할 때, 플레이 버튼을 누르면서 “클릭”음과 화면이 검게 변하는 순간, 또는 칼날이 노트를 통과하면서 내는 잘리는 소리에 맞추는 게 가장 용이합니다.&lt;/p&gt;
&lt;p&gt;MPC-BE에서 딜레이 옵션은 ‘O’ 키로 옵션 메뉴를 연 다음, “Audio &gt; Sound Processing” 아래 “Audio time shift (ms)” 체크박스를 활성화한 후 딜레이를 수정하시면 됩니다. -100 ms 단위로 조정하시는 걸 추천합니다. 제 헤드셋에선 약 -400 ms의 딜레이가 발생했는데, 제품별 편차와 게임에 따라 이 값이 다를 수도 있으니 조심하세요. (그리고, 이 옵션을 왜 여기에 숨겨뒀는지 모르겠습니다…)&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;MPC-BE 오디오 딜레이 옵션&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1411&quot; height=&quot;1052&quot; src=&quot;/_astro/mpc-be-audio-delay-options.BIsDfe-x_Z1eFKHY.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;둘째-ffmpeg&quot;&gt;둘째: &lt;code&gt;ffmpeg&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;그리고 &lt;code&gt;ffmpeg&lt;/code&gt;로 오디오를 옮기는 명령은 다음과 같습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 -itsoffset 0.4 -i input.mp4 -map 1:v -map 0:a -c copy output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 딜레이 값이 다르다면 위에서 변경하는 걸 잊지 마세요!&lt;/p&gt;
&lt;h2 id=&quot;딜레이-값이-반대라면&quot;&gt;딜레이 값이 반대라면&lt;/h2&gt;
&lt;p&gt;만약 오디오가 둥영상을 앞서는 경우라면, 다음 명령을 사용하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 -itsoffset 0.4 -i input.mp4 -map 0:v -map 1:a -c copy output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;첫 입력에서 딜레이가 되지 않은 둥영상 스트림과 둘째 입력에서의 딜레이된 오디오 스트림을 받아오니, &lt;code&gt;-map&lt;/code&gt; 뒤에 적힌 번호의 순서가 바뀐 점만 참조하시면 됩니다.&lt;/p&gt;
&lt;h1 id=&quot;마치며&quot;&gt;마치며&lt;/h1&gt;
&lt;p&gt;비디오 플레이어에서 딜레이 옵션을 끄는 걸 잊지 마세요. (아니면 왜 모든 영상에서 오디오 동기화 문제가 생겼는지 확인하다 미쳐버리실 수도 있습니다! 직접 경험해서 그런 건 아니고요.)&lt;/p&gt;
&lt;p&gt;그리고, 고치는 게 이렇게 쉽다면, Quest 자체적으로 왜 녹화가 끝난 다음에 이렇게 고쳐주면 안될까요…?&lt;/p&gt;</content:encoded></item><item><title>인터넷에서 내 이름 구매하기</title><link>https://ericswpark.com/ko/blog/2025/2025-03-30-buying-my-korean-name-on-the-internet/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2025/2025-03-30-buying-my-korean-name-on-the-internet/</guid><pubDate>Sun, 30 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;퓨니코드에 대해서 대화하다가, 무심결에 제 한국 이름으로 도메인이 구매 가능한지 한번 검색해봤습니다. 꽤 흔한 이름이라 당연히 없을 줄 알았죠.&lt;/p&gt;
&lt;p&gt;긴 말 않겠습니다. 여기서 확인해보세요: &lt;a href=&quot;http://xn--bh3b85gcrf.com&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;http://박선우.com&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>홈 서버를 완전히 잃어버릴 뻔했습니다</title><link>https://ericswpark.com/ko/blog/2025/2025-03-04-i-nearly-lost-my-entire-server/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2025/2025-03-04-i-nearly-lost-my-entire-server/</guid><pubDate>Tue, 04 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;1월 첫 주 정도에 한국에 있는 가족에게 인사를 하고 유학을 계속 진행하려 다시 미국으로 돌아왔습니다. 별 탈 없이 항공편으로 돌아왔는데, 비행 중에는 폰에 받아둔 영화 몇 편과 스팀덱 게임으로 시간을 보냈습니다. 그래도 항공기가 많이 흔들려서 잠도 잘 못자고, 노이즈켄슬링 헤드셋을 껴도 엔진 소리를 완전히 차단하지 못하니 긴 여정들은 그래도 별로 좋아하기 힘든 듯 합니다.&lt;/p&gt;
&lt;p&gt;기체가 착륙 후 게이트로 이동하는 사이, 폰에 비행기 모드를 해제하고 인터넷에 연결하면서 13시간 동안 밀려있던 알림들이 한꺼번에 들어오는 걸 구경했습니다. 그렇게 중요한 건 없는 것 같아, 가방을 들고 다른 분들과 비행기에서 내렸습니다.&lt;/p&gt;
&lt;p&gt;비자 체크를 마치고 체크인 가방을 찾은 후 버스들이 모여있는 정류장으로 이동했습니다. 정류장으로 연결해주는 열차를 기다리는 중, 휴대폰에 갑자기 Pushover 알림이 하나 도착합니다. 알림 소리가 중요한 알림에만 울리게 해둬서 별 좋은 얘긴 아닌 줄 직감했죠.&lt;/p&gt;
&lt;p&gt;역시, 13시간 동안 고통스런 비행 다 견뎌놓고 다음과 같은 광경을 보게 됩니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;UnRAID의 대시보드 웹UI. 디스크 묶음 안 디스크 5개 중 2개가 오류로 비활성화되어 옆에 X가 표시되어 있습니다&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2432&quot; height=&quot;532&quot; src=&quot;/_astro/initial-array-drive-failures.BMCTeHvL_2oLw2I.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;처음 든 생각은: 왜 미국까지 온 &lt;em&gt;다음에야&lt;/em&gt; 이런 일이 일어나?! 정답: 머피의 법칙. 두 번째 생각은: 듀얼 패리티로 해두기 잘했다. 처음 디스크 그룹을 설정할 때 낭비라고 생각했는데, 완전히 데이터를 날리기 직전까지 가보니 그런 생각이 전부 사라지더라고요.&lt;/p&gt;
&lt;p&gt;한꺼번에 디스크 2개가 고장났다는 점이 수상하게 여겨졌지만, 당시에는 크게 생각하지 않았습니다. (사망플래그.) 묶음 안에 들어간 디스크들은 다른 시기에 구입해서 섞었는데, 한번 구매했을 때 2개를 구매했었는지 잘 기억이 나질 않았거든요. 아마 제조 공정에 문제가 있겠지 했습니다.&lt;/p&gt;
&lt;p&gt;어쨌든 16 TB 디스크 2개를 주문해 교체하기로 하고 더 이상의 읽기/쓰기가 나머지 디스크에 무리를 줘서 고장나게 하기 전에 서버 전원을 내려뒀습니다. 그 동안 서버를 사용하지 못하니 가족이 달가워하진 않겠지만, 상황이 악화되서 처음부터 다시 시작하는 것보단 나으니까요.&lt;/p&gt;
&lt;p&gt;물론, 최악의 상황에 대처하기 위해 메인 서버에 있는 데이터를 복사해둔 백업 서버가 있긴 있습니다. 하지만, 일석이조로 처리하기 위해 백업 서버 자체는 다른 장소에서 호스팅하고 있어, 백업 스케줄 자체가 매일 모두가 잠든 새벽에만 돌아가고 그마저도 집과의 인터넷 속도 제약을 받고 있죠. 마지막 백업이 언제였냐에 따라 메인 서버에 있는 모든 데이터가 복사되지 않았을 수도 있고, 인터넷으로 거의 18 TB 정도의 데이터를 복원하는 것도 골치아픈 작업이거든요.&lt;/p&gt;
&lt;p&gt;그래서 디스크들이 도착한 다음, 가족을 원격으로 도와주며 서버 안에 설치하고, 서버를 킨 다음에 디스크 묶음을 리실버링(resilver)하여 모든 게 순조롭게 마무리되었겠죠?&lt;/p&gt;
&lt;h1 id=&quot;모든-게-순조롭게-마무리되는-세상&quot;&gt;모든 게 순조롭게 마무리되는 세상&lt;/h1&gt;
&lt;p&gt;하드 두 개가 도착한 후, 가족과 함께 화상통화로 드라이브를 바꿔끼웠습니다. 이 과정이 살짝 복잡했는데, &lt;a href=&quot;/ko/blog/2022/2022-07-04-jonsbo-n1-brief-text-review/&quot;&gt;메인 서버 케이스로 사용중인 Jonsbo N1&lt;/a&gt;은 드라이브를 바꾸기 위해 서버를 메인 케이스 레일을 따라 빼는 것을 요구로 하거든요. 서버 자체도 TV 밑에 미디어 수납장에 있다 보고, 꺼내는 과정에서 실수로 떨어뜨리기라도 한다면 나머지 드라이브들이 죽을 수도 있으니 꺼내면서 조심스레 케이블들을 연결 해제하고 서버 전체를 끄집어내야 됐습니다.&lt;/p&gt;
&lt;p&gt;그래도 결국 디스크들을 성공적으로 교체했고, 서버를 부팅시켜 복구 과정을 시작했습니다. 프리클리어(디스크 사용 전 전부 지운 후 디스크가 정상적으로 작동하는지 검증하는 과정)도 그냥 건너뛰었는데, 새로운 디스크들이 불량일 수가 없겠죠?&lt;/p&gt;
&lt;h1 id=&quot;새로운-디스크들이-불량입니다&quot;&gt;새로운 디스크들이… 불량입니다?&lt;/h1&gt;
&lt;p&gt;불행히도, 복구 시작 1분만에 새로운 드라이브들에서 똑같은 I/O 오류가 뜨기 시작했습니다. 심지어 한 디스크는 완전히 인식불가 상태로 사라져 버렸는데, 기존의 두 8 TB 디스크 중 하나와 유사한 방식으로 “고장”났죠. &lt;code&gt;smartctl&lt;/code&gt;조차 행방불명된 디스크를 찾을 수 없었습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Smartctl open device: /dev/sde failed: INQUIRY failed&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;…그리고 나머지 디스크에 대해선 셀 수도 없이 많은 오류를 내뿜었는데, SMART 값들은 모두 정상 범위에 속했고 기준상 전부 괜찮아 보였죠:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Error 385 occurred at disk power-on lifetime: 20508 hours (854 days + 12 hours)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  When the command that caused the error occurred, the device was active or idle.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  After command completion occurred, registers were:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ER ST SC SN CL CH DH&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -- -- -- -- -- -- --&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  04 61 02 00 00 00 a0  Device Fault; Error: ABRT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Commands leading to the command that caused the error were:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  CR FR SC SN CL CH DH DC   Powered_Up_Time  Command/Feature_Name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -- -- -- -- -- -- -- --  ----------------  --------------------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ef 10 02 00 00 00 a0 08      01:08:16.785  SET FEATURES [Enable SATA feature]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ec 00 00 00 00 00 a0 08      01:08:16.785  IDENTIFY DEVICE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ef 03 46 00 00 00 a0 08      01:08:16.785  SET FEATURES [Set transfer mode]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ef 10 02 00 00 00 a0 08      01:08:16.784  SET FEATURES [Enable SATA feature]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ec 00 00 00 00 00 a0 08      01:08:16.783  IDENTIFY DEVICE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dmesg&lt;/code&gt;에선 더 이해하기도 힘들고 무서운 오류들이 나타났습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: configured for UDMA/133 (device error ignored)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2: EH complete&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: exception Emask 0x0 SAct 0x0 SErr 0x0 action 0x0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: irq_stat 0x40000001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: failed command: WRITE DMA EXT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: cmd 35/00:c8:e0:a3:05/00:01:00:00:00/e0 tag 19 dma 233472 out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel:         res 61/04:c8:e0:a3:05/00:01:00:00:00/e0 Emask 0x1 (device error)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: status: { DRDY DF ERR }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: error: { ABRT }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: failed to enable AA (error_mask=0x1)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서 원인을 파악하려면 더 자세히 확인할 수 밖에 없죠. 새로운 디스크들이 기존 디스크들과 &lt;strong&gt;완전히 똑같은 방식&lt;/strong&gt;으로 불량일 수도 없고, 완전히 새 디스크들이다 보니 더더욱 그럴 가능성이 희박하니까요.&lt;/p&gt;
&lt;h1 id=&quot;깊숙하게-원인분석하기&quot;&gt;깊숙하게 원인분석하기&lt;/h1&gt;
&lt;p&gt;일단 처음으로 시도해본 건 디스크들이 꼽혀 있는 드라이브 베이에 문제가 생겼는지 확인하려, 동생과 화상통화로 드라이브 베이 안 디스크 순서를 섞어봤습니다.&lt;/p&gt;
&lt;p&gt;이상하게도, 디스크 배열을 바꾼 후, 문제들이 똑같은 디스크들에서 재발했습니다. 이 부분이 이해가 가지 않았는데, 만약 디스크들이 문제가 아니고 다른 부품이 문제라면, 어떻게 순서까지 변경한 후 동일한 디스크에서 오류가 재발할 수 있을까요?&lt;/p&gt;
&lt;p&gt;전화 상 케이블들이 전부 다 제대로 연결되어 있는지 확인해달라고 했습니다. 이 부분도 역시 힘들었는데, 화질이 나쁜 영상통화로 선이 “제대로” 꼽혀있는지 확인하는건 꽤 어렵거든요. 동생이 연결 상태를 확인하면서, Jonsbo N1 벡플레인에 꼽혀들어가는 Molex 전선 중 하나가 완전히 꼽혀 있지 않았다고 하길래 아마도 전원 문제가 아닐까 기대해봤습니다. 문제가 디스크들을 따라다니는 이유는 먼저 떨어져나가는 디스크들이 불완전한 전원 공급에 가장 취약해서가 아닐까 했고, 새로 구매한 드라이브들이 전부 다 더 큰 용량을 가지고 있어 전원 공급이 더 원활해야 오류없이 동작할 듯 하여 가설에 힘이 실렸습니다.&lt;/p&gt;
&lt;p&gt;아쉽게도, 완전한 문제 해결은 아니었습니다. 프리클리어 단계에선 문제없이 동작하는 듯하여, 프리클리어를 취소시킨 후 리빌딩 절차에 들어갔습니다. 하지만 리빌딩을 시작한지 불과 1분도 안되어 똑같은 문제가 재발했죠.&lt;/p&gt;
&lt;p&gt;그래서 다른 원인을 찾아야 했습니다. 디스크부터, 수천킬로미터 떨어진 제 화면 사이에 의심해볼만한 부분들은 다음과 같았습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SATA 벡플레인&lt;/li&gt;
&lt;li&gt;SAS에서 SATA로 (1x4) 변환하는 확장 케이블 (SFF-8087)&lt;/li&gt;
&lt;li&gt;IT 모드로 플래싱되어 HBA로 동작하고 있는 LSI RAID 카드&lt;/li&gt;
&lt;li&gt;메인보드&lt;/li&gt;
&lt;li&gt;PSU (전원공급장치)&lt;/li&gt;
&lt;li&gt;전부 다 환각이었고 제가 미쳐가고 있을 가능성?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이때 집중이 쏠린 부분이 확장 케이블이었습니다.&lt;/p&gt;
&lt;h1 id=&quot;역사는-반복한다&quot;&gt;역사는 반복한다?&lt;/h1&gt;
&lt;p&gt;UnRAID 포럼에서 도움을 요청하는 와중에, &lt;a href=&quot;https://forums.unraid.net/topic/140535-two-disks-died-after-612-upgrade/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;2023년에 제가 작성한 글&lt;/a&gt; 하나를 보게 됩니다. UnRAID 업그레이드 이후 두 디스크가 동시다발적으로 죽어버린 경우였죠.&lt;/p&gt;
&lt;p&gt;잠깐, 디스크 2개? 지금처럼?&lt;/p&gt;
&lt;p&gt;2023년 6월쯤에 이 문제가 있었었는데, 당시에 베이징에 잠시 여행을 가서 서버 원인 분석을 제대로 할 수 없었죠. 다행히도, 2주 정도 이후에 한국에 돌아와 확인해보니 SAS에서 SATA로 변환하는 확장 케이블이 문제었습니다.&lt;/p&gt;
&lt;p&gt;이게 왜 그렇냐면, Jonsbo N1 케이스는 미니ITX 케이스로 내부가 공간이 매우 비좁습니다. 너무 공간이 좁아 옆으로 튀어나온 SAS 케이블을 케이스 안에 우겨넣을려면 옆으로 접을 수 밖에 없죠. 우측으로 꺾여있는, 90도 케이블도 사용해봤지만, 벡플레인에 자리잡고 있는 SATA 연결부분까지 케이블을 보내려면 한번 접고 갈 수 밖에 없었습니다. 엎친데 덮친 격으로 SATA 쪽도 우측으로 꺾여 있어야 하는데, 안 그러면 서버 내부를 바깥 케이스 안으로 밀어넣을 때 갈려나갈 위험이 있고 만약 갈려나간다면 Jonsbo측에선 유상수리로 전환하거든요.&lt;/p&gt;
&lt;p&gt;당시에는 케이블을 새로 구입해서 바꿨더니 아무런 문제 없이 디스크들이 전부 잘 동작했습니다. 지금까지는요.&lt;/p&gt;
&lt;h1 id=&quot;공급망-문제&quot;&gt;&lt;a href=&quot;https://youtu.be/USBX2pIU71k?si=c2vsw6kMI6FQNwFq&amp;#x26;t=350&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;”공급망 문제”&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;일단 한번에 HBA 카드와 확장 케이블을 교체하기로 결정했습니다. 만약 하나라도 불량이 아니었다면, 다시 배송을 받기 위해 기다리긴 싫었고, 가격도 그렇게 큰 차이가 없었습니다. 알리익스프레스에서 중고 카드 하나는 약 30불 정도밖에 안했고, 케이블은 거기에 조금만 더 얹어서 구매할 수 있죠.&lt;/p&gt;
&lt;p&gt;하필이면, 서버 교체 부품을 구매하는 시기가 가장 끔찍한 지금이었다는게 문제였습니다. 중국의 새해 공휴일 때문에, 알리에서 여러 판매자들이 일주일 이상 휴가 기간에 들어가버렸죠. 부품이 창고에서 출고되서 중국에서 한국까지 선박으로 이동하면 2월 중순에서 말일까지 걸릴 텐데, 그럼 서버를 거의 한 달 가까이 사용할 수 없는 문제가 있었습니다.&lt;/p&gt;
&lt;p&gt;그래서 조금 더 찾다가, ASMedia라는 회사에서 만든 칩셋이 달려 나온, PCIe에서 SATA로 변환을 해주는 카드를 발견하게 됩니다. 보니 HBA 카드랑 기능은 똑같아 보여서, 혹시 사용할 수 없는지 궁금해졌습니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;알리에 나온 ASMedia PCIe SATA 카드 매물 목록&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2175&quot; height=&quot;916&quot; src=&quot;/_astro/asmedia-pcie-to-sata-card-listings.Vu_noeUq_Zn8XSk.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;예전에 어딘가에서 사용량이 증가하면 카드가 연결된 디스크들을 잃어버리는 일이 있다는 것을 기억했는데, 알고보니 이 종류의 카드가 아니라 SATA 포트 배분기 카드(port multiplier card)랑 헷갈렸다는 걸 알게 됐습니다. 마벨(Marvell)사 칩셋이 들어간 이런 카드들은 이 문제점이 있기 때문에 가급적 피해야 합니다. 일반적인 PCIe SATA 카드는 이런 문제가 있다는 경험담이나 정보를 찾을 수 없어 괜찮아 보였는데, 만약 카드가 &lt;a href=&quot;https://forums.unraid.net/topic/102010-recommended-controllers-for-unraid/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;ASM1166 칩셋 기반일 경우 정상적인 작동을 위해 간단한 펌웨어 업데이트&lt;/a&gt;가 필요한 걸 빼면 전부 다 괜찮아 보였습니다. 심지어 LSI 카드랑 비교해서 별 큰 차이가 없는데, LSI 카드를 IT 모드로 전환하기 위해선 어차피 펌웨어를 덮어씌워야 하기 때문이죠. 게다가 가격이 말도 안될 정도로 저렴했는데, 카드는 약 10불, 그리고 연결할 12개짜리 SATA 케이블 묶음을 2달러 주고 구입했습니다. 다 계산해 봐도 LSI 카드의 절반 가격보다 더 저렴하죠.&lt;/p&gt;
&lt;p&gt;그래서 알리에서 ASM1066 칩셋 기반 카드를 주문했는데, 알리의 창고에서 출고되서 그런지 새해 공휴일도 무시하고 곧바로 출고되었습니다. (자본주의 이점.) 아쉽게도, 물류 검사를 하는 평택에 있던 세관은 그렇지 않아 2월 첫째주 쯤에야 카드를 받아볼 수 있었는데, LSI 카드를 배송받는 것보단 빨랐습니다.&lt;/p&gt;
&lt;p&gt;카드가 도착했을 때, 예전 카드보다 더 서버에 잘 맞는다는 걸 발견했습니다. 예전 LSI 카드보다 공간도 덜 차지하고, 포트도 하드 디스크들이 있는 케이스 앞쪽으로 향하게 설계되어 있어, SATA 케이블을 구부릴 일도 없기에 케이블이 고장날 확률도 없애줬습니다.&lt;/p&gt;
&lt;p&gt;그래서 모든 부품들을 다시 연결한 후, 서버 전원을 키고, 디스크들이 전부 감지된 후, 새로 복구 작업을 시작하고 다 마무리되었겠죠? 그렇겠죠?!&lt;/p&gt;
&lt;h1 id=&quot;테세우스의-서버&quot;&gt;테세우스의 서버&lt;/h1&gt;
&lt;p&gt;만약 서버에 대한 거의 모든 걸 바꿨는데도… 이런 게 일어난다면:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;서버 하드 디스크에서 발생한 입출력(I/O) 오류들&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1556&quot; height=&quot;1018&quot; src=&quot;/_astro/io-error-theseus.wkzWfWE5_WxlmG.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;말도 안되는 일이지만, 무슨 이유에선지 계속 오류가 발생했습니다. 이걸 다 했는데도요:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버 하드 디스크들을 새 디스크들로 교체&lt;/li&gt;
&lt;li&gt;서버 RAID 카드를 새로운 SATA 카드로 교체&lt;/li&gt;
&lt;li&gt;카드와 백플레인 사이 케이블을 전부 새걸로 교체&lt;/li&gt;
&lt;li&gt;백플레인에 연결된 하드 순서를 무작위로 바꿔 백플레인 제품 결함 없음을 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;뭐가 문젠지 더 이상 생각조차 나질 않아서, &lt;a href=&quot;https://forums.unraid.net/topic/186431-io-errors-on-brand-new-hard-drives-cables-and-sata-controller-card&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;UnRAID 포럼에다가 조언을 다시 구했습니다&lt;/a&gt;. ATA 오류들이 어디에서 오는지 파악조차 되지 않는 상황이었거든요. &lt;a href=&quot;https://forums.unraid.net/topic/186431-io-errors-on-brand-new-hard-drives-cables-and-sata-controller-card/#findComment-1521969&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;포럼에서 관리자 @JorgeB가 답변을 달아줬는데, 아직도 연결 상태 — 특히 전원 쪽에 문제가 있다는 의견이었습니다&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;불행히도 실험할 예비 PSU가 없었지만, 갑자기 방안이 하나 떠올라서 동생한테 전화를 걸었습니다. 연결되어 있는 Molex 커넥터를 한 칸씩 옆으로 이동해서 연결하면 혹시 연결 길이 때문에 문제가 해결될지 궁금했기 때문이죠. (여기에서 말하는 한 칸씩은, Molex 케이블에 병렬로 4개의 Molex 커넥터가 자리잡고 있는데, PSU에서 가장 멀리 떨어진 2개의 커넥터 대신 더 가까운 걸 사용하도록 다시 연결을 시도했습니다.)&lt;/p&gt;
&lt;p&gt;이때 백플레인에 있는 연결 부위가 손이 잘 닫지 않는 부분에 있어, 동생이 연결을 해제하는데도 애를 먹었습니다. 연결 해제 과정이 오래 걸리면서, 혹시나 열 때문에 연결 부분이 녹아서 잘 빠지지 않을까 하는 생각도 들었죠. 만약 커넥터가 녹아버렸다면 연결 부위가 느슨해 접촉 불량도 생길 수 있고요. 아쉽게도 화질이 나쁜 영상 통화로는 확인을 할 수 없었고, 보내준 사진으로도 이 부분은 판독이 어려웠습니다.&lt;/p&gt;











&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;center&quot;&gt;&lt;img alt=&quot;서버 밑에 거의 보이지도 않는 Molex 케이블&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1152&quot; height=&quot;1437&quot; src=&quot;/_astro/server-underside-grainy-photo.Dosb3qLi_Z1KFqw7.webp&quot; srcset=&quot;&quot;&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;&lt;em&gt;‘ChingLung’ 글귀가 새겨진 굵은 전선 밑에 4가닥으로 만들어진 Molex 케이블이 보이세요?&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;다시 서버를 (수십번째) 조립을 마치고선, 전원이 켜진 후 프리클리어를 시작해서 입출력 문제가 사라졌는지 확인하기 시작했습니다.&lt;/p&gt;
&lt;h1 id=&quot;첫고비-통과하기&quot;&gt;첫고비 통과하기&lt;/h1&gt;
&lt;p&gt;UnRAID의 프리클리어 시스템은 다섯 단계로 구성되어 있는데, 그 중 세 단계만 디스크 전체를 건드리게 됩니다. 운이 없게도 (아니면 운좋게도?) 이번에 프리클리어하는 디스크들이 자그만치 16 TB나 되었죠.&lt;/p&gt;
&lt;p&gt;기다리는 것도 조마조마했지만, 프리클리어를 취소하지 않고 끝까지 확인하도록 내버려 두었습니다. 만에 하나 디스크들을 복구하는 과정에서 다시 전원부 쪽에 숨어있던 문제가 발현된다면 더 골치아픈 상황이 될 수도 있으니까요. 그래서 그대로 프리클리어가 돌아가면서, 수시로 상태를 확인했습니다.&lt;/p&gt;
&lt;p&gt;각 단계마다 약 20시간 정도가 소요되었는데, 16 TB짜리 디스크 상 모든 바이트를 읽거나 지우는데 20시간씩 걸린 셈입니다.&lt;/p&gt;
&lt;p&gt;다행히도, 62시간이 지날 시점에 프리클리어가 성공적으로 마쳤습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                              Unraid Server Preclear of disk _____JFA                             #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                            Cycle 1 of 1, partition start on sector 64.                           #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 1 of 5 - Pre-read verification:                  [20:44:44 @ 214 MB/s] SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 2 of 5 - Zeroing the disk:                       [20:46:40 @ 213 MB/s] SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 3 of 5 - Writing Unraid&apos;s Preclear signature:                          SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 4 of 5 - Verifying Unraid&apos;s Preclear signature:                        SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 5 of 5 - Post-Read verification:                 [20:44:42 @ 214 MB/s] SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#       Cycle elapsed time: 62:16:35 | Total elapsed time: 62:16:36                                #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   S.M.A.R.T. Status (device type: default)                                                       #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   ATTRIBUTE                   INITIAL CYCLE 1 STATUS                                             #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Reallocated_Sector_Ct       0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Power_On_Hours              3       65      Up 62                                              #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Reported_Uncorrect          0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Airflow_Temperature_Cel     28      41      Up 13                                              #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Current_Pending_Sector      0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Offline_Uncorrectable       0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   UDMA_CRC_Error_Count        0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#       Report genereated on: February 08, 2025 at 13:00:38                                        #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;--&gt; ATTENTION: Please take a look into the SMART report above for drive health issues.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;--&gt; RESULT: Preclear Finished Successfully!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 조금 지난 후, 두 번째 16 TB 디스크 역시 프리클리어를 마쳐 희망이 살짝 생겼습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                              Unraid Server Preclear of disk _____DA5                             #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                            Cycle 1 of 1, partition start on sector 64.                           #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 1 of 5 - Pre-read verification:                  [21:27:40 @ 207 MB/s] SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 2 of 5 - Zeroing the disk:                       [21:30:35 @ 206 MB/s] SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 3 of 5 - Writing Unraid&apos;s Preclear signature:                          SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 4 of 5 - Verifying Unraid&apos;s Preclear signature:                        SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 5 of 5 - Post-Read verification:                 [21:27:42 @ 207 MB/s] SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#       Cycle elapsed time: 64:26:28 | Total elapsed time: 64:26:28                                #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   S.M.A.R.T. Status (device type: default)                                                       #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   ATTRIBUTE                   INITIAL CYCLE 1 STATUS                                             #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Reallocated_Sector_Ct       0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Power_On_Hours              3       67      Up 64                                              #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Reported_Uncorrect          0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Airflow_Temperature_Cel     28      41      Up 13                                              #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Current_Pending_Sector      0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Offline_Uncorrectable       0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   UDMA_CRC_Error_Count        0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#       Report genereated on: February 08, 2025 at 15:10:38                                        #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;--&gt; ATTENTION: Please take a look into the SMART report above for drive health issues.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;--&gt; RESULT: Preclear Finished Successfully!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;복구-시작하기&quot;&gt;복구 시작하기&lt;/h1&gt;
&lt;p&gt;복구를 시작하기 앞서, “손상된” 디스크들을 교체할 때 더 큰 용량의 디스크로 대체하는 바람에 복구 과정이 생각보다 조금 더 복잡했습니다.&lt;/p&gt;
&lt;p&gt;일단 UnRAID에선 무결성 (parity) 디스크보다 더 큰 데이터 디스크를 구성하는 것을 허용하지 않기 때문입니다. 또한, 서버 상 사용 가능한 용량은 데이터 디스크들의 용량들을 전부 합친 것에 불과하기 때문에 두 8 TB 디스크들을 16 TB로 교체한다고 해서 당장 더 용량이 증가하는 것도 아니죠. 즉, 새로운 디스크들을 무결성 디스크로 구성해야만 하고, 이후에 만약 8 TB보다 더 큰 (물론, 무결성 디스크들 때문에 16 TB보단 작아야 하겠죠) 디스크로 데이터 디스크를 교체한다면, 그제서야 사용 가능한 용량이 증가하게 됩니다. 현재로선 이렇게 바꿈으로써 그냥 미래에 대비가 조금 더 잘 되었다고 보는게 맞습니다.&lt;/p&gt;
&lt;p&gt;불행히도, 오류가 난 디스크 중 한 디스크는 데이터 디스크였는데, 아까 설명해드렸듯이 UnRAID에선 무결성 디스크보다 더 큰 데이터 디스크로 교체하는 것을 허용하지 않습니다. 첫 8 TB만 사용하게 하고, 나머지는 무결성 디스크들이 전부 업그레이드된 시점에 사용하게 해도 괜찮을 것 같은데 말이죠.&lt;/p&gt;
&lt;p&gt;그나마 이러한 상황에 대비해서 “무결성 교체” (parity-swap) 절차가 있는데, 새 디스크들을 무결성 디스크들로 설정하고, 나머지 8 TB짜리 무결성 디스크를 데이터 디스크 슬롯에 옮겨넣으면 됩니다. 그럼 UnRAID에서 이전 디스크에서 새 디스크로 무결성 데이터를 복사하려는 것을 인식하고, 완료된 후 예전 무결성 디스크 위에 데이터 디스크의 내용을 복구하게 됩니다.&lt;/p&gt;
&lt;p&gt;이렇게 디스크들을 슬롯에 구성한 후, 복사 버튼을 눌러 절차를 시작했습니다. 복사 도중에는 서버를 사용할 수 없지만, 절차 도중에 오류만 나지 않더라도 감사하는 마음이었습니다. 그래도 마지막으로 한번 놀래켜주고 싶었는지, 복구 도중에 이런 무시무시한 알림도 보내줬죠:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;복구 과정 도중 서버에서 온 Pushover 알림&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1440&quot; height=&quot;1597&quot; src=&quot;/_astro/rebuild-scare.DFarhLuD_Z6aLvx.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이 알림은 “흔한 문제 해결사” (“Fix Common Problems”) 플러그인이 서버가 이미 저하된 상태에서 돌아가고 있음을 경고해주려고 보내줬지만, “오류”라고 적힌 문구를 봤을 때 복구 도중에 디스크들이 전원 문제로 또다시 없어지거나 한 줄 알았습니다.&lt;/p&gt;
&lt;p&gt;다행히도, 그런 문제는 일어나지 않았고, 몇 시간 후 무결성 교체 작업도 성공적으로 마무리되었습니다. 사실 프리클리어 단계들보다 더 오래 걸렸는데, 16 TB 디스크에 8 TB 디스크의 정보를 복사해올 때 아마도 8 TB짜리 디스크가 끝부분을 복사하면서 더 느려지기 때문에 그런 것 같습니다.&lt;/p&gt;
&lt;p&gt;이후 데이터와 무결성 디스크들의 복구 절차를 시작했는데, 두 디스크를 한꺼번에 복구할 수 있게 되어 있었습니다. 이 부분이 가장 오래 걸렸는데, 약 27시간 (!) 정도 소요된 후 성공적으로 마쳤습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;서버에서 온, 무결성 동기화와 데이터 디스크 복구가 완성되었음을 알리는 Pushover 알림&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1440&quot; height=&quot;408&quot; src=&quot;/_astro/sync-complete.Db1j_vIN_Tzfmg.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;그렇게 &lt;strong&gt;28일&lt;/strong&gt;동안 사용할 수 없었던 서버가 다시 원상복구되면서 상황이 종료되었습니다.&lt;/p&gt;
&lt;h1 id=&quot;원인-분석&quot;&gt;원인 분석&lt;/h1&gt;
&lt;p&gt;Molex 연결 부위를 건드린 후 처음에는 일시적으로 문제가 해결되다가 결국 완전히 복구가 완료되었음을 감안해서, 전원부 쪽에 집중하겠습니다.&lt;/p&gt;
&lt;p&gt;일단, 전력량으로 확인했을때, 시스템 상 사양으로는 전원을 공급하기에 충분했습니다. PSU는 Lian Li사의 SP750 모델을 사용중인데, 사양표로 계산했을때 NAS에서 사용하는 전원은 최대 380 W 정도밖에 되지 않습니다. PSU가 공급할 수 있는 전력의 절반 정도니, 제한을 초과해서 생기는 문제는 결코 아니었습니다.&lt;/p&gt;
&lt;p&gt;그러면 벡플레인까지 전원을 공급해주는 단일 케이블은 어떨까요? 시스템을 처음 조립했을 때, 이렇게 연결하는 방법 밖에 없었는데, Jonsbo 케이스의 벡플레인은 Molex 포트가 2개가 연결되는 것을 필요로 하지만, Lian Li사에서 제공하는 Molex 케이블은 분리되는 케이블 중 하나밖에 없거든요.&lt;/p&gt;
&lt;p&gt;그럼 케이블이 고장날 때까지 용량을 초과해서 문제가 발생했을까요? 한번 계산해보겠습니다.&lt;/p&gt;
&lt;p&gt;일단, 현재 NAS 안에 사용되고 있는 두 드라이브 모델의 사양표는 다음과 같습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.seagate.com/www-content/datasheets/pdfs/exos-x16-DS2011-1-1904US-en_US.pdf&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Seagate Exos X16 16 TB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://documents.westerndigital.com/content/dam/doc-library/en_us/assets/public/western-digital/product/internal-drives/wd-blue-hdd/product-brief-western-digital-wd-blue-pc-hdd.pdf&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Western Digital Blue 8 TB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예전에도 설명했듯이, 16 TB 드라이브 2개, 그리고 8 TB 드라이브 3개를 돌리고 있습니다. 시게이트의 사양표에는 각 디스크 당 최대 전력량이 10 W라고 적혀 있는데, 디스크들이 처음 시작할 때 사용하는 전력은 나열하지 않습니다만, 10 W보단 당연히 높겠죠. WD는 12 V 레일에 최대 순간 전류량을 1.75 A로 표기하는데, 그럼 처음 시작할 때 사용하는 피크 전력은 약 21 W 정도가 됩니다. WD가 디스크들이 읽고 쓸 때 사용되는 평균 전력량을 6.2 W로 표기하는데, 시게이트의 전력량을 비교해봤을 때 동급으로 치부해본다면 시게이트의 처음 시작할 때 사용하는 피크 전력은 약 31 W 정도로 추정해 볼 수 있기에 그 숫자를 가지고 계산해보겠습니다.&lt;/p&gt;
&lt;p&gt;그러면 디스크들이 처음 전원을 켰을 때 돌아가는 전력을 합산해보면 약 125 W 정도를 찍는데, 일반적으로 디스크들이 돌아가고 있을 땐 평균 전력 사용량이 약 38.6 W가 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://linustechtips.com/topic/1384462-maximum-wattage-on-1x-molex-cable-with-4-connectors/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;LTT 포럼글&lt;/a&gt;을 참조해보면, 단일 Molex 케이블은 12 V 레일에서 약 120 W의 전력을 공급해 줄 수 있습니다. 그러면 디스크들이 전부 시작할 때마다, 이 제한을 약 5 W 정도로 초과함을 알 수 있습니다. 물론, 이는 시게이트의 디스크들이 WD 디스크의 최대 순간 사용량과 똑같이 비례한다고 가정했을 경우에만 적용되기 때문에, 실제로는 그러지 않을 가능성이 있습니다. 그리고 일반적인 사용환경 아래에는, 케이블의 최대 전력량에 가까이 오지도 않죠.&lt;/p&gt;
&lt;p&gt;그러면 어떻게 문제가 발생했는지 한번 가정해보겠습니다. 2022년 6월부터 줄곧 서버를 돌려왔기 때문에 거의 3년 동안 서버를 돌린 셈이 됩니다. 이 기간 동안, 아마도 케이블에 걸린 부하가 최대 전력량을 살짝 초과하면서, 전원 연결부가 과부하 아래의 열기에 살짝 녹으며 전원 연결핀들에 접촉 불량을 일으켰을 듯 합니다. 게다가 벡플레인 바로 위에 하드 디스크들이 돌아가면서 진동이 곧대로 전해져, 전원 케이블이 연결 포트에서 조금씩 느슨해지다가, 어느 시점에는 접촉 불량에서 초래된 불충분한 전압을 몇몇 하드 디스크들이 감당할 수 없어 서버에서 연결 해제돼 입출력 오류를 일으킨 듯 합니다.&lt;/p&gt;
&lt;p&gt;Molex 플러그들을 한 열 이동해서 다시 꼽았을 때, 새 플러그들은 아직도 상태가 괜찮았기 때문에 벡플레인 포트와 접촉이 원할하게 이루어질 수 있어 문제가 해결된 듯 합니다.&lt;/p&gt;
&lt;p&gt;이걸 조금 더 빨리 추론해서 새로운 부품들을 주문하고 기다리는 과정을 생략할 기회가 있었는데, 한번에 두 디스크가 동시다발적으로 서버에서 죽어버렸을 때 디스크가 문제가 아니었음을 직감했어야 했습니다. 제조일자가 동일하더라도, 제조 도중 편차 그리고 사용 패턴에 차이 때문에 똑같은 순간에 죽는 경우는 매우 드물거든요. 물론, 이건 다 겪어보고 나서야 알게 된 사실이니 미래에 참조하면 될 듯 합니다.&lt;/p&gt;
&lt;h1 id=&quot;얻게-된-교훈-정리&quot;&gt;얻게 된 교훈 정리&lt;/h1&gt;
&lt;p&gt;물론, 만약 위에 적어둔 가정이 맞다면 이렇게 연결해 둔 것도 임시적인 해결책밖에 되지 않습니다. 케이블의 최대 전원 사양을 조금이라도 초과한다면 1-2년 정도만 되더라도 똑같은 문제가 발생할테니까요.&lt;/p&gt;
&lt;p&gt;제대로 해결하기 위해서 추후에 단일 Molex 케이블 2개를 구매해서 PSU와 벡플레인 사이에 각각 연결할 계획입니다. 케이블에서 오는 전력 손실도 최소화하고 저항도 낮추기 위해 케이블 전선은 가장 굵은 전선으로 구성하면, 전력 손실에서 오는 열기도 최소화할 수 있죠.&lt;/p&gt;
&lt;p&gt;일단 다행히도 홈서버가 복구가 완료되었고, 이 사태에서 데이터가 손실되지도 않았고 백업에서 복원할 필요도 없어 서버의 내구성은 계획한 대로 동작함을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;다음번에 이런 일이 발생할 경우 주문한 부품들이 오는데 기다리며 서버가 다운된 기간을 최소화하기 위해, 예비용 하드 디스크도 최소한 하나 구비해놓을 예정입니다. Jonsbo N1 케이스는 한번에 5개의 디스크만 설치할 수 있고 모든 베이를 사용중이기 때문에, 콜드 스페어(cold spare, 대체하는데 관리자 개입이 필요한 예비용 부품)로밖에 구성할 수 없지만, 중국에서 디스크들이 오는데 몇 주동안 기다리는 것보단 낫죠.&lt;/p&gt;
&lt;p&gt;(참고로, 예비용 디스크를 1개 초과해서 구입하는 건 다음 이유들 때문에 별로 필요하지 않다고 생각합니다. 과거 추이를 바탕으로 디스크들은 계속 저렴해지고 있고, 아직도 2개 이상의 디스크들이 동시다발적으로 죽는 경우는 확률적으로 0에 수렴한다고 생각하거든요. 아이러니하게도 이 블로그 글을 작성하고 있긴 하지만, 이 경우에도 하드 디스크들은 아무런 문제가 없었고 처음부터 케이블 부분을 확인했다면 전혀 디스크들을 구매할 필요가 없었을테니까요.)&lt;/p&gt;
&lt;p&gt;그럼 글을 마치면서, 만약 이상한 입출력 문제가 부품들을 교체했음에도 계속 발생하는 똑같은 상황을 겪고 있다면 한번 시스템의 전원부 쪽을 확인하고, 케이블들이 포트에 제대로 연결됐는지 확인하면서 문제해결에 이 글이 도움이 됐으면 합니다!&lt;/p&gt;</content:encoded></item><item><title>cargo flamegraph로 프로파일링</title><link>https://ericswpark.com/ko/blog/2025/2025-01-25-profiling-with-cargo-flamegraph/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2025/2025-01-25-profiling-with-cargo-flamegraph/</guid><pubDate>Sat, 25 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이번 글을 비교적 짧게 작성되었는데, 하고 싶었던 게 고작 이 flamegraph를 보여드리기 위해서입니다. 수강하고 있는 프로그래밍 경시대회 코스에 문제들 중 하나를 뽑아 한번 프로파일링을 돌려봤습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;flamegraph&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1200&quot; height=&quot;822&quot; src=&quot;/_astro/flamegraph.DJ3oIKHL_20KA58.svg&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;(참고: SVG 파일 안에 내용물과 상호작용이 가능하지만 사진을 우클릭한 후 새 탭에서 열어야 정상적으로 동작합니다!)&lt;/p&gt;
&lt;p&gt;&lt;del&gt;flamegraph 보기 너무 좋아요&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;참고: 윈도우에서 &lt;code&gt;cargo flamegraph&lt;/code&gt;를 실행하기 위해선 사전 준비가 약간 필요합니다.&lt;/p&gt;
&lt;h1 id=&quot;윈도우-사전준비&quot;&gt;윈도우 사전준비&lt;/h1&gt;
&lt;ol start=&quot;0&quot;&gt;
&lt;li&gt;만약 BitLocker가 활성화되어 있다면 리커버리 키를 백업해두세요. 조금 뒤에 필요합니다. 최근 윈도우 머신들은 대부분 활성화되어 출고됩니다.&lt;/li&gt;
&lt;li&gt;관리자 권한 터미널에서 &lt;code&gt;bcdedit /set dtrace on&lt;/code&gt;을 실행해 &lt;code&gt;dtrace&lt;/code&gt; 기능을 키세요.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/microsoft/DTrace-on-Windows&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;마소의 공식 &lt;code&gt;dtrace&lt;/code&gt; 레포지터리에서 설치 프로그램을 다운로드한 후 실행하세요&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_NT_SYMBOL_PATH&lt;/code&gt; 시스템 환경 변수를 다음과 같이 설정하세요 (&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/dxtecharts/debugging-with-symbols#using-the-microsoft-symbol-server&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;왜 이 값인지는 여기를 참고하세요&lt;/a&gt;):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;srv*c:\symbols*https://msdl.microsoft.com/download/symbols&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;컴퓨터를 재시작한 후, 만약 물어본다면 BitLocker 리커버리 키를 입력하세요.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;리눅스-사전준비&quot;&gt;리눅스 사전준비&lt;/h1&gt;
&lt;p&gt;리눅스 상에선 커널 버전에 맞는 &lt;code&gt;linux-tools&lt;/code&gt; 패키지들을 필요로 합니다. 전 NixOS를 쓰기 때문에, &lt;code&gt;nix shell nixpkgs#linuxKernel.packages.linux_latest_libre.perf&lt;/code&gt;을 실행하여 &lt;code&gt;flamegraph-rs&lt;/code&gt;가 필수로 요구하는 &lt;code&gt;perf&lt;/code&gt; 패키지만을 설치했습니다. (&lt;code&gt;libre&lt;/code&gt; 부분은 왜 있는지 모르겠지만 그게 안 적혀있는 패키지들을 없더라고요…) 자세한 정보는 디스트로에 맞는 설명을 위해 &lt;a href=&quot;https://github.com/flamegraph-rs/flamegraph?tab=readme-ov-file#installation&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;공식 README의 설치 부분&lt;/a&gt;을 참고하세요.&lt;/p&gt;</content:encoded></item><item><title>Rust에서 블록 단위로 버퍼링하기</title><link>https://ericswpark.com/ko/blog/2025/2025-01-23-buffering-by-block-in-rust/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2025/2025-01-23-buffering-by-block-in-rust/</guid><pubDate>Thu, 23 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이번 학기에 프로그래밍 대회 관련 코스를 수강하고 있는데, 사용하고 있는 (&lt;a href=&quot;https://kattis.com&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Kattis&lt;/a&gt;라는) 플랫폼이 얼마나 빠른 I/O를 요구하는지 올바른 정답들도 가끔씩 시간 초과(Time Limit Exceeded, TLE)로 거부되는 케이스가 종종 있었습니다.&lt;/p&gt;
&lt;p&gt;그래서 Rust에서 읽고 쓰는 성능을 조금만 더 끌어올릴 수 있는지 알아보다가, &lt;code&gt;stdout&lt;/code&gt;(표준 출력)이 기본으로 줄단위 버퍼링(line-buffered)을 사용하고 있다는 점을 알게 됐습니다. 즉, 다음과 같은 괜찮아 보이는 코드 샘플도, 콘솔에 출력할 때 낼 수 있는 이론적인 최대 성능에 못미친단 점이죠:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;rust&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Instant&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Instant&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;500000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        println!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;{}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, i);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; duration &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;elapsed&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    println!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;걸린 시간: {:?}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, duration);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;실행하면 다음과 같은 출력을 보게 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(...)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;499997&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;499998&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;499999&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;걸린 시간: 17.5983885s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 왜 그런지 알아보겠습니다. &lt;code&gt;println!()&lt;/code&gt; 매크로를 호출할 때마다, &lt;code&gt;stdout&lt;/code&gt;에 줄바꿈 문자(&lt;code&gt;\n&lt;/code&gt;)를 출력하는데, &lt;code&gt;stdout&lt;/code&gt;이 이러한 줄바꿈 문자를 볼 때마다 버퍼를 비우기 (flush) 때문입니다. 버퍼를 비우려면 시스템 콜(syscall)을 실행하는데, 버퍼의 내용을 콘솔에 출력하거나 만약 리디렉트를 해둔다면 파일에 작성하거나 어디든지 일단 보내버리죠.&lt;/p&gt;
&lt;p&gt;문제는 시스템 콜을 실행할 때마다 드는 비용이 있는데, 프로세서가 사용자 모드에서 커널 모드로 전환하고 레지스터 값들을 이리저리 옮기고 해야 할 일이 많기 때문입니다. 그리고 이걸 5백만번이나 반복합니다! 비용이 쌓일 수 밖에 없죠.&lt;/p&gt;
&lt;p&gt;해결 방법은 &lt;code&gt;stoudt&lt;/code&gt; 앞에 &lt;code&gt;BufWriter&lt;/code&gt;를 두면 되는데, 처음 볼 땐 이상해 보이고 목적에 부합하지도 않는 듯 합니다 (원래 버퍼링을 덜 하는게 목적이었잖아요?) 자세히 보면 &lt;code&gt;BufWriter&lt;/code&gt;는 작성하려고 하는 내용을 별도의 버퍼에 저장하는데, &lt;code&gt;BufWriter&lt;/code&gt;에 &lt;code&gt;.flush()&lt;/code&gt;를 호출한다면 이 버퍼의 내용을 한꺼번에 &lt;code&gt;stdout&lt;/code&gt;에 던저버리고 똑같이 &lt;code&gt;.flush()&lt;/code&gt;를 불러 결과적으로 &lt;code&gt;stdout&lt;/code&gt;을 블록 단위로 버퍼링하듯이 만들어버립니다.&lt;/p&gt;
&lt;p&gt;처음에는 &lt;code&gt;stdout&lt;/code&gt;이 &lt;code&gt;BufWriter&lt;/code&gt;의 버퍼에서 오는 내용 중 줄바꿈 문자를 볼 때마다 어차피 쓰기 시스템 콜을 호출할 것 같아 이 방식이 작동하지 않을 줄 알았는데, 실제로 성능이 향상되서 작동하는 것을 보여줍니다.&lt;/p&gt;
&lt;p&gt;변경된 코드는 다음과 같습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;rust&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;io;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;io&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BufWriter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Instant&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Instant&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; output &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; io&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stdout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lock&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; mut&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; buffer &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BufWriter&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(output);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;500000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        writeln!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(buffer, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;{}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, i)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;unwrap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    buffer&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;flush&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;unwrap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; duration &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;elapsed&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    println!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;걸린 시간: {:?}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, duration);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;거짓말인 것 같다고요?&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(...)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;499997&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;499998&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;499999&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;갈린 시간: 3.7357123s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 왜 Rust는 &lt;code&gt;stdout&lt;/code&gt; 작동 방식을 줄단위 버퍼링과 블록 단위 버퍼링 간 변경하게 해 주지 않을까요? &lt;a href=&quot;https://github.com/rust-lang/rust/issues/60673&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;GitHub 이슈 트래커 상 이슈가 존재하긴&lt;/a&gt; 합니다만, &lt;a href=&quot;https://github.com/rust-lang/rust/pull/78515&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;연관된 pull request&lt;/a&gt;가 2022년에 닫혀버려서 지난 3년 동안 문제가 계속 방치되어 왔습니다. (만약 기존 이슈까지 본다면 거의 6년 동안 남겨진 셈이죠!)&lt;/p&gt;
&lt;p&gt;따라서 언어의 공식 코드가 되기 전까진, 위에 나와 있는 우회방안이 crate (Rust 라이브러리) 없이 Rust에서 더 빠른 I/O를 얻는 최고의 선택지인 듯 합니다. 물론, 버퍼링 없는 raw &lt;code&gt;stdout&lt;/code&gt;을 얻기 위해 &lt;a href=&quot;https://github.com/rust-lang/rust/issues/58326#issuecomment-1802406085&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;GitHub의 @WieeRd님께서 작성하신 이 우회방안&lt;/a&gt;을 시도해보시거나, &lt;a href=&quot;https://docs.rs/grep-cli/0.1.11/grep_cli/fn.stdout.html&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;ripgrep처럼 조건부 버퍼링&lt;/a&gt;을 해보실 수 있습니다.&lt;/p&gt;
&lt;p&gt;Kattis가 crate 사용을 허가해줬다면 얼마나 좋았을까요.&lt;/p&gt;</content:encoded></item><item><title>리눅스에서 KT의 구식 와이파이에 연결할 때</title><link>https://ericswpark.com/ko/blog/2025/2025-01-04-kts-outdated-wifi-on-linux/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2025/2025-01-04-kts-outdated-wifi-on-linux/</guid><pubDate>Sat, 04 Jan 2025 11:15:00 GMT</pubDate><content:encoded>&lt;p&gt;만약 스타벅스 매장 등에 있는, KT의 공공 와이파이에 리눅스 노트북으로 접속해 보셨다면 연결에 실패하는 것을 겪어보셨을 겁니다. 제 경우에는 KDE가 비밀번호가 틀렸다고 경고를 했는데, 비밀번호가 틀릴 수 없는게 안드로이드 폰에선 똑같은 비밀번호로 와이파이에 연결이 잘 됐거든요.&lt;/p&gt;
&lt;p&gt;만약 로그를 확인하신다면 다음과 같은 것을 보셨을 겁니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 kernel: wlp3s0: associate with 06:09:b4:78:b3:13 (try 1/3)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 kernel: wlp3s0: associate with 06:09:b4:78:b3:13 (try 2/3)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 kernel: wlp3s0: RX AssocResp from 06:09:b4:78:b3:13 (capab=0x431 status=0 aid=1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 kernel: wlp3s0: associated&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 wpa_supplicant[1408]: wlp3s0: Associated with 06:09:b4:78:b3:13&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-EAP-STARTED EAP authentication started&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-SUBNET-STATUS-UPDATE status=0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=25&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-EAP-METHOD EAP vendor 0 method 25 (PEAP) selected&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: SSL: SSL3 alert: write (local SSL3 detected an error):fatal:protocol version&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: OpenSSL: openssl_handshake - SSL_connect error:0A000102:SSL routines::unsupported protocol&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-EAP-FAILURE EAP authentication failed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 kernel: wlp3s0: disassociated from 06:09:b4:78:b3:13 (Reason: 23=IEEE8021X_FAILED)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-DISCONNECTED bssid=06:09:b4:78:b3:13 reason=23&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-SSID-TEMP-DISABLED id=0 ssid=&quot;KT_starbucks_Secure&quot; auth_failures=2 duration=31 reason=AUTH_FAILED&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: Added BSSID 06:09:b4:78:b3:13 into ignore list, ignoring for 10 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: BSSID 06:09:b4:78:b3:13 ignore list count incremented to 2, ignoring for 10 seconds&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이게 왜 실패하냐면, KT의 와이파이는 WPA2-Enterprise를 사용하는데, TLS v1.2보다 더 낮은 버전을 사용해서 그렇습니다. &lt;a href=&quot;https://github.com/openssl/openssl/discussions/22642&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;보안 문제 때문에 OpenSSL에선 이 버전들을 기본으로 비활성화하거든요&lt;/a&gt;. 심지어 &lt;a href=&quot;https://wiki.archlinux.org/title/NetworkManager#WPA_Enterprise_connections_fail_to_authenticate_with_OpenSSL_%22unsupported_protocol%22_error&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Arch 위키에도 서술되어 있습니다&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;대략적으로 2021년 3월경에 TLS v1.0과 1.1이 &lt;em&gt;드디어&lt;/em&gt; 지원 종료된 건 맞지만, TLS v1.2는 2008년부터 사용할 수 있었고, (최신 버전인) TLS v1.3은 2018년부터도 사용이 가능했습니다. 아직까지도 KT가 구식 보안 프로토콜을 사용하는 것에 대해 핑계도 댈 수 없죠.&lt;/p&gt;
&lt;p&gt;해결하기 위해 보안이 취약한 TLS 버전을 활성화하는 것보다, 그냥 비밀번호가 걸려있지 않은 와이파이에 접속한 다음 Tailscale의 출구 노드 (exit node) 기능을 사용해서 연결을 암호화시켰습니다. 덜 번거롭고 정보 탈취도 막아주니까요.&lt;/p&gt;
&lt;p&gt;맨날 TV에 AI 기술의 최강자라고 홍보하고 다니는 회사 맞습니다. 혹시 KT는 수치심이란 걸 알까요?&lt;/p&gt;</content:encoded></item><item><title>DD-WRT 깔린 WRT32X 순정펌으로 되돌리기</title><link>https://ericswpark.com/ko/blog/2025/2025-01-04-reverting-dd-wrted-wrt32x-back-to-stock/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2025/2025-01-04-reverting-dd-wrted-wrt32x-back-to-stock/</guid><pubDate>Sat, 04 Jan 2025 11:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이삿짐을 정리하다가 예전에 중국에서 사용하던 공유기를 찾았습니다. 당시에 Astrill이란 VPN 서비스를 사용했는데, 네트워크 전체에 VPN을 제공해주는 DD-WRT 애플릿이 있어서 DD-WRT로 플래시한 기억이 납니다. 그렇게 네트워크 전체에 VPN을 설정하게 되면, 기기마다 어플을 깔고 VPN을 설정해야 하는 수고를 덜 수 있거든요.&lt;/p&gt;
&lt;p&gt;이제 (비교적) 인터넷이 자유로운 나라에 있다 보니, 다시 OpenWrt를 설치하기로 했습니다. 하지만 온라인에 나와 있는 글들은 전부 다 순정 펌웨어를 시작으로 작성되어 있네요.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dd&lt;/code&gt;로 파티션 삽질을 하다가 공유기 벽돌을 만드는 걸 방지하기 위해, 순정 Linksys 펌웨어를 올린 다음에 OpenWrt로 변환하기로 했습니다. 그런데 &lt;a href=&quot;https://forum.dd-wrt.com/phpBB2/viewtopic.php?t=316094&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;DD-WRT 포럼 상 순정 상태로 되돌리는 포럼 글&lt;/a&gt;에선 DD-WRT가 펌웨어를 제대로 파티션 테이블에 플래시하려면 일정 비트 수가 앞에 추가되는 방식으로 개조된 펌웨어 이미지를 까는 것을 요구한다고 적혀 있었습니다. &lt;a href=&quot;https://forum.dd-wrt.com/phpBB2/viewtopic.php?p=1158363#1158363&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;이미지를 여기에서 다운로드&lt;/a&gt;할 수 있는 것처럼 보이는데, 실제로 다운로드를 하려면 DD-WRT 포럼에 계정을 만들어야지, 만들지 않으면 포럼 소프트웨어가 다운로드를 막아버립니다.&lt;/p&gt;
&lt;p&gt;임시 계정을 만들어서 이미 개조된 이미지를 다운로드하려고 시도했는데, DD-WRT 포럼 자체가 방치된지 시간이 꽤 흐른 듯 합니다. 계정 활성화를 위한 확인 이메일은 도착조차 하지 못했고, 포럼 관리자분들께 보낸 이메일도 답장을 아직까지 받지 못했스니다.&lt;/p&gt;
&lt;p&gt;그럼 &lt;a href=&quot;https://forum.dd-wrt.com/phpBB2/viewtopic.php?p=1097563#1097563&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;수동으로 순정 이미지를 개조하는 절차&lt;/a&gt;를 따라야겠군요. 순서대로 진행을 했는데, 새로운 순정 펌웨어는 파티션 레이아웃이 다른 점을 몰라서 앞에 붙여야 할 데이터 비트 수가 다른 것을 몰랐습니다. 물론, 펌웨어를 파티션에 작성한 후 공유기를 반벽돌 상태로 만들기 전까지는 눈치채지 못했지만요. 다행히도, A/B 파티션 조합으로 부팅에 실패해서 다른 파티션으로 자동 변경되어, 제대로 변환된 이미지를 사용해서 다시 되살릴 수 있었습니다.&lt;/p&gt;
&lt;p&gt;혹시 DD-WRT가 플래싱된 WRT32X를 가지고 계시고 순정 상태나 OpenWrt로 가실려고 한다면, &lt;a href=&quot;https://archive.org/details/wrt32x-padded-image-dd-wrt-to-stock&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;제가 만들어 둔 이미지를 사용하실 수 있습니다&lt;/a&gt;. (&lt;a href=&quot;https://files.catbox.moe/qd2dot.img&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Catbox 상 미러&lt;/a&gt;) SHA256 체크섬으로 다운로드 검증 후 사용하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fdf5914007d02ee86a2a93d3e20bd677403ec1c9849d8126840c79126507239c&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;공유기 자체는 사용을 하지 않을 듯 합니다. 두 파티션 모두 성공적으로 OpenWrt로 변환했는데, 공개 소스 드라이버인 &lt;code&gt;mwlwifi&lt;/code&gt;와 펌웨어의 조합으로 송신 파워를 조정할 수 없게 만들어 두었기 때문에 2.4 GHz 밴드가 상시 100% 출력으로 작동해서 5 GHz 신호를 완전히 없애버립니다:&lt;/p&gt;
&lt;iframe src=&quot;https://tilde.zone/@ericswpark/113742272920262190/embed&quot; class=&quot;mastodon-embed max-w-full border-0 mx-auto&quot; width=&quot;800&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt; &lt;script src=&quot;https://tilde.zone/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
&lt;p&gt;언젠간 누군가 펌웨어를 분해해서 고칠 수 있겠죠?&lt;/p&gt;</content:encoded></item><item><title>Astro로 사이트를 옮겼습니다!</title><link>https://ericswpark.com/ko/blog/2024/2024-12-29-i-migrated-my-site-to-astro/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-12-29-i-migrated-my-site-to-astro/</guid><pubDate>Sun, 29 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;만약 이 블로그 글을 읽고 계신다면, &lt;a href=&quot;https://astro.build/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Astro&lt;/a&gt; 기반의 새로운 사이트를 읽고 있습니다!&lt;/p&gt;
&lt;h1 id=&quot;공지사항&quot;&gt;공지사항&lt;/h1&gt;
&lt;p&gt;새로운 사이트로 전환하면서 알고 계셔야 할 중요한 사항만 나열하겠습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;만약 &lt;strong&gt;RSS 리더기를 사용&lt;/strong&gt;하여 제 블로그를 구독하고 있으시면, URL을 &lt;code&gt;https://ericswpark.com/ko/blog/index.xml&lt;/code&gt;에서 &lt;code&gt;https://ericswpark.com/ko/blog/rss.xml&lt;/code&gt;로 수정해주셔야 됩니다. 만약 예전 피드 URL에 구독되어 있으실 경우 관련 경고가 표시될 수 있습니다.&lt;/li&gt;
&lt;li&gt;몇몇 하위 링크가 &lt;code&gt;/pages&lt;/code&gt; 하위 폴더로 이전했는데, 예를 들자면 &lt;code&gt;/pages/tools&lt;/code&gt;나 &lt;code&gt;/pages/fun&lt;/code&gt; 등에서 확인하실 수 있습니다. 리디렉션 공지를 띄워뒀지만 가급적 빨리 북마크를 업데이트해 주세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;전환하는-동기&quot;&gt;전환하는 동기&lt;/h1&gt;
&lt;p&gt;사용해본 웹사이트 프레임워크가 하도 많아서 기억하기조차 어렵습니다. 예전에 WordPress, Ghost, 그리고 Jekyll 등을 사용하다가 마침내 2021년에 &lt;a href=&quot;https://gohugo.io&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Hugo&lt;/a&gt;에 정착했습니다. 마지막 두 프레임워크는 GitHub Pages를 호스팅으로 사용했는데, 학생으로선 무료가 제일 좋거든요.&lt;/p&gt;
&lt;p&gt;Hugo를 선택했을 때, 앞으로 몇 년은 사이트를 이대로 유지할 생각이었습니다. 전체적으로 봤을때 웹사이트는 괜찮았지만, 몇 가지 아쉬운 점들이 있었습니다.&lt;/p&gt;
&lt;p&gt;일단, 디자인을 하도 못해서 (아직도 못하지만), 당시에 이미 만들어진 &lt;a href=&quot;https://github.com/lxndrblz/anatole/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;anatole&lt;/a&gt;이란 테마를 사용했습니다. 쓸 수 있게 배포해 주셔서 너무 감사했지만, 제가 기반해서 만든 사이트가 제가 만든 것처럼 느껴지지 않았고, 아무리 스타일을 변경해봐도 변함이 없었습니다.&lt;/p&gt;
&lt;p&gt;스타일 변경을 얘기해서 말인데, Hugo로 스타일링을 하는 게 꽤 어려웠습니다. Hugo는 구글의 Go 언어를 기반으로 작성되어 있는데, 추가로 배워야 하는 독자적 템플릿 문법이 있습니다. 사소한 변경을 할 때마다 이 문법을 배워야 된다는 점이 사이트를 바꾸는데 큰 애로사항이 되어버려, 사이트 자체 디자인이 꽤 오랫동안 방치되어 왔죠.&lt;/p&gt;
&lt;p&gt;하지만 Markdown 파일을 작성하고 커밋하여 GitHub에 올리는 것만으로 블로그 글을 작성할 수 있다는 점이 좋았습니다. 게다가 Hugo하고 제가 쓰던 anatole 테마는 번역 기능(i18n)을 잘 지원했는데, 저는 블로그 글을 영어로도 쓰기 때문에 꼭 필요로 하는 기능이었습니다. 너무 쓰기 간편해서, 사실 2021-23년 군대에 복역할 때도 아이패드 상 &lt;a href=&quot;https://workingcopy.app/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Working Copy&lt;/a&gt;라는 어플로 Markdown 파일을 작성하고 GitHub에 올려 블로그에 계속 글을 올릴 수 있었습니다.&lt;/p&gt;
&lt;p&gt;그러나 최종적으로 이전을 선택한 이유는 기존 사이트에 JavaScript를 추가하는 것이 너무 어려워서였습니다. 사이트에 여러 도구들을 만들어 뒀는데, 만들때마다 그냥 JS를 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그들 안에 집어넣고 Hugo가 Markdown 파일 안 HTML을 렌더링하도록 설정했습니다. (&lt;a href=&quot;https://github.com/gohugoio/hugo/issues/6581&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;강제하는 설정값이 쓸데없이 무서운 &lt;code&gt;unsafe&lt;/code&gt;로 시작하게 만들어뒀는데 무슨 생각인지 싶습니다.&lt;/a&gt;) 하지만 도구들과 페이지들의 크기와 범위가 넓어지자 더 많은 기능과 의존 패키지를 추가해야 됬는데, 그럴려면 JS 번들러(bundler)를 사용해서 JS를 최종 형태로 컴파일해야 하죠.&lt;/p&gt;
&lt;p&gt;문제는 이게 Hugo에 내장된 기능이 아니기 때문에, &lt;code&gt;webpack&lt;/code&gt;을 추가로 붙여놓아 JS를 컴파일해야 됐습니다. 어느 정도 괜찮게 작동하긴 했지만, 빌드 과정이 더욱 더 복잡해진다는 단점이 있었습니다. 더 큰 문제는 소스 코드를 어떻게 정리할지, 그리고 정리한 후 webpack이 이를 Hugo가 사용할 수 있는 번들 파일로 깨끗하게 컴파일하는 방법을 찾지 못했습니다. 게다가 위에서 설명했듯이 수정을 할 때마다 템플릿 언어를 배워야 한다는 점 때문에 결국 다른 프레임워크를 사용하기로 결정했습니다.&lt;/p&gt;
&lt;h1 id=&quot;첫-선택-nextjs&quot;&gt;첫 선택: NextJS&lt;/h1&gt;
&lt;p&gt;예전에 만든 개인 프로젝트와 대학 프로젝트에서 NextJS를 사용해본 적이 있어, 이번에 새로 사이트를 만들 때도 유용할 거라 생각했습니다.&lt;/p&gt;
&lt;p&gt;아쉽게도, 약 40% 이전을 마친 상태에서 NextJS의 내장 MDX 컴파일러가 사진 파일을 상대 경로로 불러올 수 없다는 것을 알게 됐습니다. &lt;a href=&quot;https://github.com/hashicorp/next-mdx-remote&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;next-mdx-remote&lt;/code&gt; 또한 이 기능을 지원하지 않았고요&lt;/a&gt;. 이게 왜 문제가 되냐면, 이미 Markdown으로 작성한 블로그 글이 많이 있는데 (정확히는 67개 — 영어 번역을 포함해서 134개), 각 글마다 이렇게 정리해두었기 때문입니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;blog/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  2024/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    2024-12-29-i-migrated-my-site-to-astro/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      en.mdx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ko.mdx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      imgA.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;상대 경로로 사진을 불러올 수 없다면 다음과 같은 코드를 &lt;code&gt;ko.mdx&lt;/code&gt;에 작성할 수가 없죠:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;jsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; imgA &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;./imgA.png&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Astro로 사이트를 &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;이전했습니다&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Image&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; src&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{imgA}&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;대책 해결방안은 폴더 속 모든 사진들을 &lt;code&gt;public/&lt;/code&gt; 폴더로 옮기는 것이었는데, 이를 매번 하는 것도 번거롭고 정리가 되지 않아 이렇게 진행하고 싶지는 않았습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/hashicorp/next-mdx-remote&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;mdx-bundler&lt;/code&gt;는 상대 경로 불러오기를 지원하는 듯 했는데&lt;/a&gt;, 빌드 과정 중 별다른 번들러를 추가로 설치하는 것을 필요로 했고, 이미 Hugo와 &lt;code&gt;webpack&lt;/code&gt; 때문에 이전하는 건데 왜 굳이 이렇게 해야 하나 싶었습니다.&lt;/p&gt;
&lt;p&gt;이렇게 애로사항을 겪고 있을 때, &lt;a href=&quot;https://www.purduehackers.com/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Purdue Hackers&lt;/a&gt; (제가 다니는 퍼듀 대학교의 개발 동아리) Discord에 물어보니 가장 많이 나온 추천이 Astro였습니다. 그래서 익숙한 프레임워크를 사용할 수 없음에도 불구하고 다음으로 Astro를 한번 사용해보기로 했습니다. 아마도 Astro가 더 좋을 수도 있겠죠?&lt;/p&gt;
&lt;p&gt;(스포: 더 좋죠. 왜냐하면 이 블로그 글을 읽고 있으니까요)&lt;/p&gt;
&lt;h1 id=&quot;매끄럽지-못한-사이트-전환-시작&quot;&gt;매끄럽지 못한, 사이트 전환 시작&lt;/h1&gt;
&lt;p&gt;그래서 11월 29일경 사이트 이전을 시작했으니, 전부 전환해 오는데 약 한 달이 걸렸네요. 그런데 왜 이리 오래 걸렸는지 좋은 이유를 댈 수 있습니다.&lt;/p&gt;
&lt;p&gt;일단 이전을 시작하기 전, 스스로를 위해 정해둔 몇 가지 요구사항들입니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;모든 URL은 그대로 유지되어야 됨. 만약 변경이 있을 경우, 리디렉션이나 공지를 설정할 수 있어야 함.&lt;/li&gt;
&lt;li&gt;RSS 피드도 똑같이 유지되어야 되며, 구독자들이 글을 읽는데 별다른 불편사항이 없어야 함&lt;/li&gt;
&lt;li&gt;테마 및 언어 변경, &lt;a href=&quot;https://giscus.app/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Giscus 댓글&lt;/a&gt; 등 이전 사이트에서의 모든 기능을 구현해야 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;시작하기도 무섭게, 바로 문제가 생겨버렸습니다. Astro가 이전 URL인 &lt;code&gt;https://ericswpark.com/ko/blog/index.xml&lt;/code&gt;에선 RSS 피드를 렌더링하는 것을 거부했거든요. 그래서 바로 &lt;a href=&quot;https://github.com/withastro/astro/issues/12675&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;버그 리포트&lt;/a&gt;를 접수했는데, 이번 주 전까지는 아무런 답변이 없었습니다. Astro 대표 개발자들과 대화를 나눈 끝에, 버그가 어디에서 발생되는지 알아내어 &lt;a href=&quot;https://github.com/withastro/astro/pull/12815&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;pull request를 따로 열었습니다&lt;/a&gt;. Astro Discord 서버 상에선 개발자가 이를 확인해주겠다고 했는데, 이 글을 쓰는 시점에도 아직까지 패치되지 않았습니다. 하지만 &lt;code&gt;patch-package&lt;/code&gt;로 패치할 수 있고 나중에 통합이 되면 그냥 패치를 제거하면 되니 크게 문제될 건 없습니다.&lt;/p&gt;
&lt;p&gt;만들다가 요구사항 2번은 조금 덜 빡빡하게 해도 괜찮을 것 같아 URL을 &lt;code&gt;index.xml&lt;/code&gt;에서 &lt;code&gt;rss.xml&lt;/code&gt;로 바꿨습니다. 더 적절해 보이고, 나중에 JSON 형식 (&lt;code&gt;rss.json&lt;/code&gt;)처럼 다른 포맷도 추가할 수 있기 때문이죠.&lt;/p&gt;
&lt;p&gt;기말고사 기간이 끝나고 나서 사이트 마이그레이션에 계속 매달렸습니다. 거의 대부분을 처음부터 다시 작성해야 되서 시간이 좀 오래 걸렸습니다. Github Gist 임베드 등을 위한 컴포넌트를 작성해야 했고, 스타일링은 CSS로 만들었으며 (이때 &lt;a href=&quot;https://tailwindcss.com/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Tailwind CSS&lt;/a&gt;가 도움이 많이 됐죠), 라우팅 — 특히 번역 라우팅을 제가 원하는 대로 일일히 설정해야 했습니다.&lt;/p&gt;
&lt;p&gt;근데 그걸 다 만든다면…?&lt;/p&gt;
&lt;h1 id=&quot;좋은-것들만-모여-있는-곳&quot;&gt;좋은 것들만 모여 있는 곳&lt;/h1&gt;
&lt;p&gt;Astro가 정말 좋은 게, 생각할 수 있는 거의 모든 것이 기본으로 지원된다는 점입니다. MDX로 작성하고 싶다고요? &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/mdx/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Astro에선 지원하죠.&lt;/a&gt; 코드 문법 하이라이팅을 원하신다고요? &lt;a href=&quot;https://docs.astro.build/en/guides/syntax-highlighting/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Astro에선 지원하죠.&lt;/a&gt; Tailwind CSS? &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/tailwind/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;지원한다고요.&lt;/a&gt; React 추가? &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/react/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;당연하죠.&lt;/a&gt; Vue도 같이? &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/vue/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;왜 안되겠어요.&lt;/a&gt; 서버쪽 렌더링? &lt;a href=&quot;https://docs.astro.build/en/guides/on-demand-rendering/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;옵션값만 바꾸면 되죠.&lt;/a&gt; 정적 사이트 렌더링? 이미 기본값입니다. 사용설명서도 충실하고 &lt;code&gt;.astro&lt;/code&gt; 파일은 그냥 JS에 frontmatter와 비슷한 부분을 추가해서 연장한 것에 불과하기 때문에, 프레임워크에 익숙해지는데 그리 많이 걸리지도 않았습니다.&lt;/p&gt;
&lt;p&gt;그리고 CSS를 처음부터 작성하였기에, 사이트를 더욱 더 유연하고 자유롭게 원하는 대로 변경할 수 있었습니다. (그렇게 얘기하지만 아직도 디자인 감각이 없는게 문제죠. 홈페이지는 그래도 예쁘다고 해주세요.) 또 Astro는 스타일링 규칙이 서로 충돌하지 않도록 분리된 스타일링 기능도 있어 매우 편했습니다. 물론, 전역 스타일이 가끔씩 문제를 일으켜서 피곤하게 하는 경우도 있지만, 그럴 때는 그냥 스타일링 규칙을 구체적으로 지정해주면 아예 문제를 피할 수 있죠.&lt;/p&gt;
&lt;p&gt;그리고 처음으로 마이그레이션을 시작할 때 겪은 문제를 제외하면 나머지는 너무 매끄럽게 진행되었고, 원했던 모든 요구사항들을 충족할 수 있었습니다.&lt;/p&gt;
&lt;h1 id=&quot;비교하기&quot;&gt;비교하기&lt;/h1&gt;
&lt;p&gt;만약 궁금하시다면 예전 사이트와 새 사이트를 비교하는 사진들을 확인해보세요:&lt;/p&gt;

&lt;script type=&quot;module&quot; src=&quot;/home/runner/work/ericswpark.github.io/ericswpark.github.io/src/components/embeds/ImgComparison.astro?astro&amp;type=script&amp;index=0&amp;lang.ts&quot;&gt;&lt;/script&gt; &lt;img-comparison-slider class=&quot;dark:hidden&quot;&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/before-home-light-ko.43-9ropm.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;이전 홈페이지 (라이트 모드)&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/after-home-light-ko.Dh1Om8Jl.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;새 홈페이지 (라이트 모드)&quot;&gt; &lt;/img-comparison-slider&gt;
 &lt;img-comparison-slider class=&quot;hidden dark:block&quot;&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/before-home-dark-ko.CuuP387f.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;이전 홈페이지 (다크 모드)&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/after-home-dark-ko.D7q6Xbzh.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;새 홈페이지 (다크 모드)&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;br/&gt;

 &lt;img-comparison-slider class=&quot;dark:hidden&quot;&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/before-blog-post-light-ko.DdlLRqwy.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;이전 블로그 글 예시 (라이트 모드)&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/after-blog-post-light-ko.BFlN1FOC.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;새 블로그 글 예시 (라이트 모드)&quot;&gt; &lt;/img-comparison-slider&gt;
 &lt;img-comparison-slider class=&quot;hidden dark:block&quot;&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/before-blog-post-dark-ko.DF20vQ9G.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;이전 블로그 글 예시 (다크 모드)&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/after-blog-post-dark-ko.BOO08r-i.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;새 블로그 글 예시 (다크 모드)&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;특정하자면 다음 부분들이 가장 마음에 듭니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;언어 전환 버튼이 클릭 두 번에서 한 번으로 줄었습니다. 만약 페이지 번역이 존재하지 않다면 다른 언어의 메인 페이지로 이동할지 물어보는데, 이전 사이트에선 그냥 사라져버리는 것보다 제대로 구현됐다고 생각합니다.&lt;/li&gt;
&lt;li&gt;테마 전환 버튼을 사용해서 시스템 테마를 따라가게 할 수 있습니다. 조금 숨겨져 있는 게 문제인데, 버튼을 두 번 우클릭하거나 “정보” 페이지에 가셔서 “사이트 설정” 섹션으로 스크롤하시면 됩니다.&lt;/li&gt;
&lt;li&gt;홈페이지. 예전에 이미 얘기했나요? (호버링 효과 너무 좋아하는 1인)&lt;/li&gt;
&lt;li&gt;사이트 하단 부분과 재미&lt;del&gt;있는&lt;/del&gt;없는 글귀.&lt;/li&gt;
&lt;li&gt;더 효율적으로 공간을 사용하고 내용에 집중한 점. 사이드바가 글을 읽는 동안 공간을 차지하는 것이 싫었는데, 이젠 더 이상 볼 필요가 없죠.&lt;/li&gt;
&lt;li&gt;그리고 Astro를 사용하면 언제든지 원하는 대로 사이트를 개편할 수 있죠. 이미 이 프레임워크가 &lt;em&gt;얼마나&lt;/em&gt; 유연한지 위에서 설명했듯이요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;물론, 사이트는 추후해도 계속 개선할 예정입니다. 하지만 일단 가장 큰 마이그레이션이 끝났으니까, 드디어 작성을 미루고 있던 블로그 글들에 다시 집중할 수 있게 됐습니다.&lt;/p&gt;
&lt;h1 id=&quot;그게-전부입니다&quot;&gt;그게 전부입니다!&lt;/h1&gt;
&lt;p&gt;새 사이트가 마음에 드셨으면 좋겠습니다! 만약 제안이나 피드백이 있으시면 이메일을 통해 연락주세요. 특히 존재하지 않는 링크가 있다면 제보해 주시면 감사하겠습니다. (죽은 링크를 하도 싫어해서 다 가져왔다고 확신하지만)&lt;/p&gt;
&lt;p&gt;그리고 이번 마이그레이션을 기회로 사이트에 조그만 이스터 에그를 숨겨뒀습니다. 개인적으로 귀여운 것 같아요. 만약 찾으시면 이메일이나 Discord로 알려주세요! (사이트 하단 부분 글귀 아닙니다.)&lt;/p&gt;</content:encoded></item><item><title>Bluesky 도착!</title><link>https://ericswpark.com/ko/blog/2024/2024-11-24-now-on-bluesky/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-11-24-now-on-bluesky/</guid><pubDate>Sun, 24 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/ericswpark.com&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;이제 Bluesky에서 만나실 수 있습니다!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;몇 가지 생각을 끄적인다면:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;도메인 검증하는 방식이 너무 마음에 듭니다. 마스토돈처럼 프로필 링크백을 확인하는 것도 괜찮지만, 실제 DNS 레코드를 추가하는게 더 “공식적”인 것처럼 느껴집니다. 아마도 여럿이서 하려면 너무 귀찮아지겠지만, 이 경우에 대비해 &lt;code&gt;.well-known&lt;/code&gt; 파일을 추가하는 다른 방식도 제공하고 있죠.&lt;/li&gt;
&lt;li&gt;트위터/X와 완전히 똑같은 인터페이스지만, 나치들을 볼 필요가 없습니다. 더 소개할 필요가 있나요?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;전반적으로, 트위터의 대체제로 자리잡을 것 같습니다. 마스토돈은 연합 방식 등이 매우 혁신적이었는데, 전반적인 구조나 복잡한 카드열 레이아웃이 일반 사용자가 쓰기에 너무 혼란스러웠습니다.&lt;/p&gt;
&lt;p&gt;이제 이렇게만 지속되길 바래야죠. 세 번째는 성공하겠죠?&lt;/p&gt;</content:encoded></item><item><title>한중일 입력기와 게임에서의 Esc 키 버그</title><link>https://ericswpark.com/ko/blog/2024/2024-11-19-the-esc-key-bug-in-games-with-cjk-imes/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-11-19-the-esc-key-bug-in-games-with-cjk-imes/</guid><pubDate>Tue, 19 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;최근에, 꽤 오래된 “Spec Ops: The Line”이란 3인칭 슈팅 게임을 즐기고 있습니다. 하도 오래된 게임이다 보니, 자잘한 버그가 많은 편인데, 예를 들자면 게임의 일부 부분이 주사율에 맞춰져 있어 만약 모니터가 고주사율 모니터면 클리어가 거의 불가능에 가까워진다거나, 아니면 전체화면 모드로 진입 시 다중 모니터들의 해상도가 깨져버리는 문제 등이 있죠.&lt;/p&gt;
&lt;p&gt;하지만 이런 버그들 중 가장 짜증나는 버그는 게임을 일시정지할 수 없다는 점이었습니다. Esc 키를 눌러 일시중지 메뉴를 키려고 시도하면, 메뉴가 순간적으로 나타났다가 바로 사라져버리는 현상이 나타났습니다. 임시방편 해결책으로, 게임에서 Alt-Tab해서 나간 다음, 다시 진입 후 게임이 다시 화면에 그려지기 전 재빠르게 Esc 키를 두번 누르면 그제서야 메뉴가 사라지지 않았습니다.&lt;/p&gt;
&lt;p&gt;운이 없게도, 일시정지 메뉴 외에는 게임에서 저장하고 나가는 방법이 없기 때문에 무슨 문제인지 알아내려고 했습니다. 이상하게도, 온라인에서 동일한 증상을 겪고 있는 사용자들을 확인활 수 없었습니다. SOTL은 최근에 스팀에서 게시가 삭제된 만큼, 관심이 예전보다 살짝 높아져 만약 모두에게 문제가 되는 부분이라면 온라인에 올라올 수 밖에 없다고 생각했습니다. 이걸 겪고 있는 사용자들이 없다면, 남은 선택지로는 게임이 제 환경과 시스템과 함께 작동하는 과정에서 생기는 버그일 수 밖에 없죠.&lt;/p&gt;
&lt;p&gt;일단, 윈도우가 여러 입력 기기 때문에 예상치 못한 이상한 짓을 하는지 확인해보기 위해 모든 외장 키보드와 마우스를 연결 해제해 봤습니다. 하지만 버그가 지속되자, 입력기를 한글 입력기에서 영어로 바꾸니 곧바로 버그가 사라졌습니다…?&lt;/p&gt;
&lt;p&gt;그래서 온라인에서 검색해보니, 다른 게임들에서도 이러한 버그가 있었습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/RocketLeague/comments/18yxl2z/anyone_have_a_thing_where_when_you_press_escape/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;로켓 리그&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/Borderlands2/comments/13wrzfb/esc_key_not_working_bug_fix/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;보더랜드 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;왜 그런진 모르겠지만, 제 예상으로는:&lt;/p&gt;
&lt;p&gt;한중일 입력기는 영어와 다르게 한 글자를 입력할 때마다 여러 키조합을 통해서 글자를 입력합니다. 영어로는 ‘a’ 한 입력이 글자 하나에 부합하지만, 한국어는 ‘가’를 입력하려면 ‘r’ 키와 ‘k’ 키를 눌러야 하는 것처럼 말이죠.&lt;/p&gt;
&lt;p&gt;그럼 만약 ‘가’ 대신 ‘ㄱ’와 ‘ㅏ’를 분리해서 입력하고 싶다면 어떻게 해야 할까요? 만약 입력 도중 Esc 키를 누르게 된다면 입력기가 알파 문자 (즉, 한국어에선 글자의 첫 자음) 입력 모드로 전환한 다음, 다음 글자를 조합하기 위해서 키 입력을 기다리게 됩니다.&lt;/p&gt;
&lt;p&gt;제 예상으로는 IME에서 Esc 키에 대한 이벤트를 전송할 때, 프로그램에다가 알파 문자 입력 모드로 전환됨을 알리는 이벤트를 추가로 전송하는 듯 합니다. 이렇게 이벤트를 전송하면 프로그램이 현재 작성되고 있는 글자 아래에 있는 밑줄을 취소하고, 사용자는 다음으로 누르는 키들이 새로운 글자를 조합하는데 사용됨을 알 수 있죠. 이때 Esc 키를 기다리고 있는 게임 엔진들은, Esc 키를 한번 눌렀음에도 불구하고 두 번 누른 것이라고 착각해버리고 버그가 발생하는 듯합니다.&lt;/p&gt;
&lt;p&gt;아쉽게도, &lt;a href=&quot;https://www.nirsoft.net/utils/keyboard_state_view.html&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;KeyboardStateView for Windows&lt;/a&gt;와 같은 프로그램으로 증상 재현을 시도해봤지만 실패했습니다. 아마도 한중일 입력기와 테스트해 볼 생각조차 하지 못한 버그투성이 게임 엔진 코드 탓인 듯 합니다. 꽤 이상한 버그네요!&lt;/p&gt;</content:encoded></item><item><title>아이폰 배터리 교체 후기: 애플이 타인 심카드를 줬습니다?</title><link>https://ericswpark.com/ko/blog/2024/2024-10-07-iphone-battery-swap-review-apple-sent-me-soneone-elses-sim/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-10-07-iphone-battery-swap-review-apple-sent-me-soneone-elses-sim/</guid><pubDate>Mon, 07 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;아이폰 배터리 성능이 80%로 닳아버려 한 일주일 전 쯤 수리를 위해 애플에 아이폰을 택배로 보냈습니다. (사실 진단 프로그램에서는 80%보다 더 낮은 77%로 표기됐는데, 설정 페이지에서는 애케플이 만료되는 걸 지켜보라는 듯 마냥 80%에서 떨어지질 않았습니다. 다행히도 지원팀에 연락했더니 진단 프로그램에서 제가 본 동일한 값이 나와 수리를 승인해줬습니다.)&lt;/p&gt;
&lt;p&gt;일주일 지나니 오늘이 되어 폰이 다시 왔습니다. 상자를 열었을 때 처음으로 나온 건 항목별 수리 명세서였는데, 애플이 배터리와 무슨 이유에선지 &lt;em&gt;조도 센서&lt;/em&gt;를 바꿨다고 표기되어 왔습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;바뀐 부품의 번호를 표기한 항목별 수리 명세서&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2568&quot; height=&quot;2175&quot; src=&quot;/_astro/itemized-receipt.j2r5QbpV_RuIe6.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;제 추측으론 수리 도중 센서가 고장났거나, 평상시 사용할 때는 괜찮았다가 진단할 때 통과하지 않아서 교체되지 않았을까 싶습니다. 지난 2년 간 사용하면서 조도 센서가 오작동하는 건 못 느꼈거든요.&lt;/p&gt;
&lt;p&gt;실제 아이폰이 들어있는 상자-속-상자를 열었을 때, 처음으로 눈치챈 건 원래 부착되어 있던 강화유리가 없어져 있었습니다. 당연히 배터리를 교체하려면 화면을 열어야 하니 그건 이해가 되죠. 하지만, 화면을 열 때 조심해서 분리하지 않았는지, 금속 도구로 프레임을 긁힌 자국이 여럿 보였습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;아이폰 화면 밑 부분에 난 긁힌 자국들&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3000&quot; height=&quot;4000&quot; src=&quot;/_astro/damaged-frame.C7mUgmup_Z2lpjwX.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;물론 자세히 안 보면 눈치채지 못하겠지만, 그래도 실망할 수 밖에 없죠. 엄청 깨끗하게 사용했고, 택배로 부칠 땐 거의 완벽한 상태로 보냈는데, 누구든지 받았을 때 상태가 보냈을 때 상태와 비슷할 거라 예상합니다. (특히 애플 공식 수리 센터에 접수했을 경우에는 더욱 더 그렇죠.) 댓글로 애플을 옹호하기 전에, 만약 사설 수리 센터에서 똑같은 자국들을 냈다면 똑같이 얘기하실 수 있으세요?&lt;/p&gt;
&lt;p&gt;어쨌든, 그게 전부가 아니었습니다. 전원을 켰을 때 초기 설정 화면이 나왔는데, 수리하면서 폰을 공장 초기화한 듯 합니다. (당연히 백업은 해뒀죠.) 하지만 설정 과정을 거치는데, 이상하게도 상태바에 신호가 뜨면서 “5G 광대역” 표시가 옆에 나타났습니다. 제 심카드는 아직 넣지도 않았습니다…?&lt;/p&gt;
&lt;p&gt;알고 보니 애플 직원이 실수로 수리된 제 아이폰에 &lt;em&gt;다른 사람의 티모바일(T-Mobile, 미국 통신사) 심카드를 넣어 보내버렸습니다&lt;/em&gt;. 아이폰 자체는 전에 적어뒀던 일련번호가 맞아서 다른 사람 아이폰을 받은 건 아니고요.&lt;/p&gt;
&lt;p&gt;티모바일에 전화를 걸어서 상황을 설명하니, 원래 주인이 심카드 재발급을 진행하면 되니 그냥 심카드를 버리라고 했습니다. 혹시 애플이 다시 달라고 요청할까봐 일단 옆에 둘 예정인데, 사실 이렇게 오는 것 자체가 잘못됐다고 생각합니다. 그리고 만약 수리를 위해 기기를 보낸다면, 언제나 케이스와 같은 악세서리와 특히 심카드를 빼고 보내세요! 이렇게 다른 사람이 실수로 심카드를 받을 수도 있고, 타인이 자신의 번호를 갖고 있어서 좋을 게 없으니까요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;총정리&lt;/strong&gt;: 배터리 교체됨, 성능 상태 100프로, 프레임 긁힘, 애플이 외로울까봐 누군가의 번호를 소개해줬습니다. 10점 만점에 6점?&lt;/p&gt;</content:encoded></item><item><title>EMS 프리미엄으로 한국에서 미국까지 택배 보낸 후기</title><link>https://ericswpark.com/ko/blog/2024/2024-09-25-shipping-packages-from-korea-to-the-us-with-ems-premium/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-09-25-shipping-packages-from-korea-to-the-us-with-ems-premium/</guid><pubDate>Wed, 25 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;최근에 한국에서 미국 인디애나주까지 휴대폰을 택배로 받아야 했습니다. 전반적인 계획은 온라인에서 폰을 주문한 다음, 한국에 있는 집으로 배송받은 다음에, 가족분이 우체국을 통해 여기까지 보내는 거였죠. 글로만 적어보면 꽤 간단해 보입니다.&lt;/p&gt;
&lt;p&gt;일단, 한국 우체국(EMS)에선 배터리가 들어간 제품을 일절 취급하지 않기 때문에 휴대폰을 발송하는 것이 불가능했습니다. 검색해보니 다른 방안은 DHL이나 페덱스같이 더욱 비싼 서비스를 사용하거나, EMS 프리미엄을 쓰는 거였습니다. UPS Korea에서 우체국 사업부랑 협업하여 제공하는 것 같은데, 가격도 나쁘지 않았습니다.&lt;/p&gt;
&lt;p&gt;EMS 프리미엄으로 휴대폰을 보내려면 작성해야 되는 서류가 몇 가지 있습니다. 하나는 휴대폰의 IMEI값, 모델명, 그리고 일련번호의 기입을 요구했습니다. 또 다른 서류에서는 휴대폰에 대한 파손 면책에 동의하는 문구가 있었습니다. (만약 택배 자체가 배송 중 분실되면 보상받는 보험은 가입이 별도로 가능했습니다.)&lt;/p&gt;
&lt;p&gt;기본 배송비로 54,000원이 나왔는데, 분실 보험을 들어서 10,000원이 별도로 추가됐습니다.&lt;/p&gt;
&lt;p&gt;택배 접수부터 도착까지 현지 시각으로 작성된 타임라인입니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2024-09-11 10:31 - 우체국에 택배 접수&lt;/li&gt;
&lt;li&gt;2024-09-11 13:22 - 우체국에서 택배 출발&lt;/li&gt;
&lt;li&gt;2024-09-11 14:36 - 동서울우편집중국에 도착&lt;/li&gt;
&lt;li&gt;2024-09-11 15:31 - 우편집중국에서 출발&lt;/li&gt;
&lt;li&gt;2024-09-11 19:59 - 국제우편물류센터에 도착&lt;/li&gt;
&lt;li&gt;2024-09-11 23:21 - 운송업체 (UPS) 인계&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;무슨 이유에서인지, 인계된 이후 변동사항이 더 이상 올라오지 않았는데, 괜히 걱정만 했습니다. EMS 프리미엄이 쓰는 배송 코드에서 UPS 코드로 전환이 되면, 새로운 코드로 배송 진행사항이 올라옵니다.&lt;/p&gt;
&lt;p&gt;이렇게 전환되는데 약 이틀이 걸렸는데, 이 기간동안 아무런 연락이 없는게 초조하긴 하지만 참을 수 없는 건 아닙니다. (아마도요.) 새로운 추적 코드로 전환되면 EMS 프리미엄 사이트에 추적 코드를 기입하면 자동으로 새 추적 코드와 함께 UPS 사이트로 전달해줍니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2024-09-13 12:19 - UPS에 라벨 생성&lt;/li&gt;
&lt;li&gt;2024-09-13 13:54 - 인천에 있는 UPS 센터에 택배 도착&lt;/li&gt;
&lt;li&gt;2024-09-13 13:59 - 센터에서 출발&lt;/li&gt;
&lt;li&gt;2024-09-13 14:04 - 인천에 다른 UPS 센터에 도착&lt;/li&gt;
&lt;li&gt;2024-09-13 15:06 - 세관 통과 대기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;대기하는 것도 꽤 길었는데, 금요일날 대기가 찍혀서 혹시나 월요일에 시작하는 추석 연휴랑 겹쳐 지연되지 않을까 걱정하고 있었습니다. 아마도 UPS나 인천 세관이 주말이나 휴일에는 운영을 안하는 줄 알았는데, 다시 업데이트가 오는 걸 봐서 아마도 하는 것 같습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2024-09-15 01:30 - 센터에서 출발&lt;/li&gt;
&lt;li&gt;2024-09-15 05:52 - 세관 통과 완료, 배송 중&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 시점으로 아마도 택배가 항공편에 실렸을 것으로 예상하고 한번 출국하는 항공편을 조회해봤습니다. 한국에서 미국으로 가는 모든 UPS 택배는 앵커리지(Anchorage)에 있는 UPS 시설을 경유해서 가는 것 같습니다.&lt;/p&gt;
&lt;p&gt;아쉽게도, 화물 항공편들은 추적이 꽤 어려운데, 추적하는 사이트들이 전부 스케줄과 위치 데이터가 다르게 표시되기 때문입니다. 그러다 &lt;a href=&quot;https://www.flightradar24.com/data/flights/5x6099#371a0a67&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Flightrader24에서 5X6099편&lt;/a&gt;을 찾았는데, 추적 페이지에 나온 시간이랑 대조해봤을때 오전 1:30에 출발한 게 맞았기 때문에 아마도 이 항공편이 아닐까 싶었습니다. 예상대로 항공편은 오후 3:35 앵커리지에 도착했고, 업데이트가 다시 올라오기 시작했습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2024-09-14 15:52 - 앵커리지에 UPS 시설에 도착&lt;/li&gt;
&lt;li&gt;2024-09-14 17:51 - 시설에서 출발&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이번에는 켄터키주(Kentucky)의 시간대로 변경이 됐습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2024-09-15 04:13 - 켄터키 UPS 시설에 도착&lt;/li&gt;
&lt;li&gt;2024-09-15 10:29 - 시설에서 수입 신고&lt;/li&gt;
&lt;li&gt;2024-09-15 14:43 - 시설에서 출발&lt;/li&gt;
&lt;li&gt;2024-09-15 17:10 - 인디애나주 UPS 시설에 도착&lt;/li&gt;
&lt;li&gt;2024-09-16 07:17 - 현지 우체국에서 처리 시작&lt;/li&gt;
&lt;li&gt;2024-09-16 08:54 - 배송 출발&lt;/li&gt;
&lt;li&gt;2024-09-16 12:42 - 배송 완료&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;정리하면 택배가 한국에서 미국까지 오는데 총 6일 걸렸습니다. 상자가 조금 찌그러져 있었지만, 안에 있는 내용물은 무사했고, 같이 포함했던 강화유리조차 깨져있지 않았습니다.&lt;/p&gt;
&lt;p&gt;단점으로 꼽자면 위에 나와있듯이 내용물 파손은 보장이 되질 않고, 택배가 크기가 클수록, 그리고 무게가 더 많이 나갈수록 가격에 기하급수적으로 많이 든다는 점입니다. 리튬 배터리가 들어있지 않아 EMS 프리미엄을 쓰지 않더라도, 캘리퍼와 같은 도구와 한국 과자가 든 상자 하나를 보내는데 거의 10만원이나 예상 금액이 나와 보내지 않았죠.&lt;/p&gt;
&lt;p&gt;물론, 왕복 비행값보단 훨씬 저렴하죠. 만약 본인이나 지인이 예정된 항공편이 없다면, 아마도 터질 수 있는 제품들을 지구 반대편으로 보내는 데 존재하는 유일한 방안이 아닐까 싶습니다.&lt;/p&gt;</content:encoded></item><item><title>Django: Postgres에서만 사용가능한 필드 금지시키기</title><link>https://ericswpark.com/ko/blog/2024/2024-08-05-django-disallow-postgres-specific-fields/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-08-05-django-disallow-postgres-specific-fields/</guid><pubDate>Mon, 05 Aug 2024 09:52:00 GMT</pubDate><content:encoded>&lt;p&gt;Django에는 특정 데이터베이스 엔진에서만 사용가능한 필드가 몇 가지 있습니다. 문제가 되는 부분은, 만약 코드에 이러한 필드를 추가하게 된다면, 프로젝트는 다른 데이터베이스 엔진과 호환되지 않게 되는데, 자동으로 생성되는 마이그레이션들이 다른 엔진들에서는 실패하기 때문입니다.&lt;/p&gt;
&lt;p&gt;그렇다고 이러한 필드를 쓰지 않는 것을 기억하는 것도 힘들고, 만약 큰 프로젝트에서 개발자들이 여럿 있을 경우 이를 지키는 것이 거의 불가능의 수준에 이르게 된다는 점입니다. 그래서 Django 시스템 체크를 사용한다면 이러한 필드들이 마이그레이션 파일을 만드는 것을 방지할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; inspect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.postgres.fields&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.apps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.core.checks &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Error, register&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# noinspection PyUnusedLocal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@register&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; disallow_postgres_specific_fields_check&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(app_configs, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    errors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    disallowed_fields &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        item[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; item &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; inspect.getmembers(django.contrib.postgres.fields, inspect.isclass)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; model &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.apps.apps.get_models():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; field &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; model._meta.get_fields():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; disallowed_field &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; disallowed_fields:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                # PyCharm 버그, 참조: https://youtrack.jetbrains.com/issue/PY-32860&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                # noinspection PyTypeHints&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                if&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; isinstance&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(field, disallowed_field):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    errors.append(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                        Error(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                            f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Field &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; cannot be used as it is a Postgres-specific field: &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                            f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;disallowed_field.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__name__}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                            hint&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Use fields that are database engine-agnostic and provided by Django.&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                            id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;config.E005&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 코드베이스에 적합한 ID를 사용하세요&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; errors&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 필요하시다면 &lt;a href=&quot;https://github.com/shipperstack/shipper/commit/cfa70d11c365a026cbae8326a55d3d40a5c13eee&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;shipper 프로젝트에 사용된 최종 커밋을 여기&lt;/a&gt;에서 찾아보실 수 있습니다.&lt;/p&gt;</content:encoded></item><item><title>새 브라우저로 바꿀 시간</title><link>https://ericswpark.com/ko/blog/2024/2024-08-05-i-need-a-new-browser/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-08-05-i-need-a-new-browser/</guid><pubDate>Mon, 05 Aug 2024 04:11:00 GMT</pubDate><content:encoded>&lt;p&gt;이번 5월에 Firefox에서 크롬으로 브라우저를 바꾸게 됐는데, 이유는 Mozilla 팀이 제가 제출한 (쓸모없는) 브라우저 확장 플러그인을 스토어에서 내려버렸기 때문입니다:&lt;/p&gt;
&lt;iframe src=&quot;https://tilde.zone/@ericswpark/112396955326625035/embed&quot; class=&quot;mastodon-embed max-w-full border-0 mx-auto&quot; width=&quot;800&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt; &lt;script src=&quot;https://tilde.zone/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
&lt;p&gt;물론, 다른 이유도 많았죠. 지난 몇 년간, Firefox 개발자들이 선택한 디자인 결정에 꽤 짜증이 났었는데, 예를 들자면 WebUSB와 같은 최신 웹 표준을 지원하지 않는데 제공한 임의적인 이유가 별로 마음에 들지 않았습니다:&lt;/p&gt;
&lt;iframe src=&quot;https://tilde.zone/@ericswpark/112792913950709534/embed&quot; class=&quot;mastodon-embed max-w-full border-0 mx-auto&quot; width=&quot;800&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt; &lt;script src=&quot;https://tilde.zone/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
&lt;p&gt;물론 Mozilla가 Firefox를 개인정보 보호에 중점을 둔 브라우저로 홍보하면서, 한편으로는 &lt;a href=&quot;https://blog.privacyguides.org/2024/07/14/mozilla-disappoints-us-yet-again-2/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;광고 추적 API와 같은 쓰레기를 만들어두는게&lt;/a&gt; 도움이 되진 않겠죠. 게다가 이런 멍청한 아이디어들을 낸 게 한두번이 아닌데, &lt;a href=&quot;https://www.theverge.com/2017/12/16/16784628/mozilla-mr-robot-arg-plugin-firefox-looking-glass&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;예전에 무슨 재미도 없는 이스터 에그를 심어두다가 발각된 적&lt;/a&gt;도 있습니다. (마케팅 팀은 도대체 무슨 마약을 하고 있었을까요?)&lt;/p&gt;
&lt;p&gt;그래서 바꾸고 나선 삶에 평온을 되찾았습니다. 약 2개월 동안요. 그러다가 크롬 설정에 이런 게 나와버렸습니다:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/chrome-ublock-origin-unsupported.XLmLpqN2_Zj6h0J.webp&quot; alt=&quot;크롬의 확장 플러그인 설정 페이지입니다. &amp;quot;이 확장 플러그인은 더 이상 지원되지 않을 수 있습니다&amp;quot; 아래 uBlock Origin이 나열되어 있습니다&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;958&quot; height=&quot;212&quot;&gt;&lt;/p&gt;
&lt;p&gt;또 브라우저를 바꿀 시간이 왔군요.&lt;/p&gt;
&lt;h1 id=&quot;필요한-기능들&quot;&gt;필요한 기능들&lt;/h1&gt;
&lt;h2 id=&quot;webusb--다른-최신-웹-api들&quot;&gt;WebUSB / 다른 최신 웹 API들&lt;/h2&gt;
&lt;p&gt;이게 아마도 충족하기 가장 어려운 조건 같아 시도조차 하지 않겠습니다. 현재로선 WebUSB를 제공하는 브라우저는 크로미움과 파생 브라우저들 뿐이거든요.&lt;/p&gt;
&lt;p&gt;다행스럽게도, 현재 이런 API들을 사용하는 사이트들은 (아직까진) 그렇게 많지 않기에, 이런 API를 필요로 하는 기기들을 사용할 때에만 쓸 크로미윰을 설치해 두면 되죠.&lt;/p&gt;
&lt;p&gt;다시 (Mozilla 개발자분들께) 말하자면, 이게 &lt;strong&gt;순정으로 지원&lt;/strong&gt;되고 제가 아두이노 보드를 플래싱하는데 Mozilla 주장대로 “사생활을 침해하는” 브라우저를 설치하지 &lt;em&gt;않아도&lt;/em&gt; 된다면 더 좋겠죠. 아쉽게도, &lt;a href=&quot;https://mozilla.github.io/standards-positions/#webusb&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Mozilla 스펙 입장 페이지에 WebUSB에 관한 입장은 기능을 추가하지 않는 쪽으로 쏠려 있습니다&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;(…) 많은 USB 기기가 USB 프로토콜을 통한 잠재적으로 악의적인 상호작용을 처리하도록 설계되지 않았고, 이러한 기기가 연결된 컴퓨터에 상당한 영향을 미칠 수 있기 때문에, USB 기기를 웹에 노출시키는 데 따른 보안 위험이 너무 광범위하여 사용자를 노출시키거나 최종 사용자에게 의미 있는 정보 제공 동의를 얻기 위해 적절하게 설명할 수 없다고 생각합니다. 또한 사이트에서 USB 기기 ID나 USB 기기에 저장된 데이터를 추적 식별자로 사용할 수 있는 위험도 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(위는 페이지 안 문단을 구글 번역기를 사용하여 번역한 부분입니다.)&lt;/p&gt;
&lt;p&gt;그럼 그냥 이 기능이 어떻게 동작하는지 &lt;em&gt;아는&lt;/em&gt; 사람들을 위해 기능 플래그 뒤에 숨기고 &lt;code&gt;about:config&lt;/code&gt; 안에 넣어버리면 되죠. 어쩌피 그곳에 넣은 설정값만 해도 많잖아요. 하나 더 추가하면 누가 뭐라 하나요?&lt;/p&gt;
&lt;p&gt;만약 WebUSB/WebBluetooth/등의 권한 요청창이 위협적인 노란색/빨간색으로 색칠된 경고 삼각형에, “이는 잠재적으로 위험한 기능입니다”라고 적혀있었다면 개인적으로 거의 대부분의 사용자들은 “거부”를 누를 거라고 생각합니다.&lt;/p&gt;
&lt;h2 id=&quot;동기화&quot;&gt;동기화&lt;/h2&gt;
&lt;p&gt;크롬의 동기화 기능은 엄청 좋습니다. 기기 간 동기화되지 않은 기록을 본 적이 개인적으로는 없었던 것 같습니다.&lt;/p&gt;
&lt;p&gt;이를 Firefox의 동기화 기능과 비교하면, Mozilla가 개발한 게 그렇게 좋지 않음이 느껴집니다. 개인적으로, 동기화 문제가 하도 많아서 미칠 지경입니다. 다른 기기로 탭 보내기 기능은 어설프고 가끔씩 아예 실패할 때도 있죠.&lt;/p&gt;
&lt;p&gt;하지만 이 기능은 포기할 수 없는데, 하던 일을 바로 다른 기기에서 이어하는 것을 엄청 중요시하기 때문입니다. 노트북에서 긴 글을 연 다음, 지하철 안에서 휴대폰으로 똑같은 글을 읽는게 기능이 없다면 URL을 복사해야 하는 골치아픈 절차로 바뀌거든요.&lt;/p&gt;
&lt;p&gt;휴대폰을 얘기해서 말인데요…&lt;/p&gt;
&lt;h2 id=&quot;휴대폰-어플&quot;&gt;휴대폰 어플&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/ko/blog/2024/2024-07-08-i-got-scammed-on-swappa&quot;&gt;몇 달전에 안드로이드로 변경하려다 문제가 생겨서&lt;/a&gt; 지금은 다시 아이폰을 사용하고 있는데, 때문에 모든 브라우저가 그냥 WebKit에 번지르르한 껍데기를 씌운것에 불과하죠. (&lt;a href=&quot;https://www.theverge.com/2024/1/25/24050478/apple-ios-17-4-browser-engines-eu&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;먄약 유럽 연합에 있다면 이야기가 달라지는데, 거기에서만 기본 인권을 보장받나 봅니다.&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;여기서도 비슷한 상황이 펼쳐지는데, 크롬은 WebKit을 사용하여 최선을 다하지만, Firefox의 iOS 어플은 꽤 버그투성이입니다. 개발자들의 탓은 아니겠지만, 몇 년전에 제가 제보한 버그 리포트들이 아직도 안 고쳐지고 남아 있습니다. (예를 들면 &lt;a href=&quot;https://github.com/mozilla-mobile/firefox-ios/issues/14279&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;이런 버그나&lt;/a&gt;, &lt;a href=&quot;https://github.com/mozilla-mobile/firefox-ios/issues/13973&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;이 버그&lt;/a&gt;, 그리고 &lt;a href=&quot;https://github.com/mozilla-mobile/firefox-ios/issues/12051&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;이 버그요&lt;/a&gt;. 그리고 제가 제보하진 않았지만 경험했던 &lt;a href=&quot;https://github.com/mozilla-mobile/firefox-ios/issues/11318&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;버그들&lt;/a&gt;도 있습니다.)&lt;/p&gt;
&lt;p&gt;안타깝지만 제가 눈여겨보던 Firefox 파생본인 LibreWolf는 휴대폰 어플이 아예 없고, 공식 위키에도 당장은 앱을 만들 계획이 없다고 나와 있습니다. 이전 동기화 기능과 더불어 이 제약은 큰 개발자 팀이 지원하지 않는 많은 브라우져들을 적어도 저의 선택지에서 현재로선 제외시켜버립니다.&lt;/p&gt;
&lt;h2 id=&quot;ublock-origin&quot;&gt;uBlock Origin&lt;/h2&gt;
&lt;p&gt;또 필요한 기능으론 uBlock Origin이 꼭 필요한데, 이 요구사항이 거의 모든 크로미윰 기반의 브라우저들을 바로 제외시킵니다. (아니면 왜 크롬에서 브라우저를 바꾸겠어요?)&lt;/p&gt;
&lt;p&gt;Brave란 크로미움 기반 브라우저는 &lt;a href=&quot;https://killedbygoogle.com/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;구글이 없애&lt;/a&gt;버릴 &lt;a href=&quot;https://github.com/brave/brave-browser/issues/20059#issuecomment-992720832&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Manifest V2를 유지시키겠다고 발표했는데&lt;/a&gt;, &lt;a href=&quot;https://x.com/BrendanEich/status/1534893414579249152&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Brave CEO의 트윗(아니면 엑싯인지 요즘에 뭐라고 하던지)에 의하면 구글이 크로미윰에서 관련 코드를 삭제하지 않는 것&lt;/a&gt;을 바탕으로 한 공지라고 합니다. 개발진은 기업 사용자들 때문에 구글이 삭제하지 않을 것이라고 생각하는데, 만약 구글이 나중에라도 없앤다면 &lt;em&gt;다시끔&lt;/em&gt; 브라우저를 바꾸고 싶은 마음은 없거든요.&lt;/p&gt;
&lt;h1 id=&quot;바꿀-브라우저는&quot;&gt;바꿀 브라우저는…&lt;/h1&gt;
&lt;p&gt;그래서 현재로선, 마지못해 Firefox로 돌아가고, 추후에 더 좋은 브라우저가 나오거나 Mozilla가 결국 죽일때까진 계속 사용할 것 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/ko/blog/2024/2024-06-28-davinci-resolve-short-codec-testing&quot;&gt;비선형 편집 시스템&lt;/a&gt;마냥 좋은 브라우저는 찾기 힘든 것 같습니다.&lt;/p&gt;</content:encoded></item><item><title>Swappa에서 사기 당한 경험담 적어봅니다</title><link>https://ericswpark.com/ko/blog/2024/2024-07-08-i-got-scammed-on-swappa/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-07-08-i-got-scammed-on-swappa/</guid><pubDate>Mon, 08 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;올해 1월에 미개봉 삼성 갤럭시 Z 폴드5를 구매했습니다. 당시에 안드로이드 어플을 개발할 폰을 찾고 있었는데, Swappa에 올라온 글이 눈에 띄었습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;원래 판매글&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;978&quot; height=&quot;758&quot; src=&quot;/_astro/original-listing.Bb6bygwl_29usFW.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;당시에는 꽤 괜찮은 가격이라고 생각했고, 폴더플 화면에서 어플들을 시험할 수 있다는 생각에 바로 구입했습니다.&lt;/p&gt;
&lt;p&gt;돈을 다 지불하고 나서 정신이 조금 들어 판매자에게 연락을 해 휴대폰이 제대로 된 폰이 맞는지 물어봤습니다. 답변은 (꽤 무례하게) 돌아왔죠:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;판매자가 훔친 폰이 아니라고 답변함&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;639&quot; height=&quot;171&quot; src=&quot;/_astro/not-stolen.DZ9vCblJ_ZhuLOS.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;할 수 있는 것이라곤 제대로 된 폰이 오길 바라면서 기다리는 것 밖에 없었습니다. 그리고 실제로 택배가 도착했을때, 잠시동안은 그렇게 아무 일 없을 것이라고 생각했죠. 원래 공장 밀봉 스티커가 다 온전하게 붙어있었고, 열어보니 안에 구글 파이 (Google Fi) 유심 카드도 삽입되어 있었습니다. 예상으로는 Fi 프로모션에서 폰을 저렴하게 할인받아 구입한 후 재판매하는 듯했습니다.&lt;/p&gt;
&lt;p&gt;그리고 다음 5개월 동안은 아무런 문제가 없었죠.&lt;/p&gt;
&lt;h1 id=&quot;문제-시작&quot;&gt;문제 시작&lt;/h1&gt;
&lt;p&gt;6월 어느날, 폰에 신호가 안 잡히는 걸 눈치챘습니다. 한국에 다시 귀국해서 휴가를 즐기고 있었는데, 미국에서 가져온 eSIM이 로밍 신호를 못 잡고 있었습니다. 처음에는 단순한 시스템 상 버그인 줄 알고, Visible(번역 메모: 미국의 MVNO 통신사명)에 연락해서 eSIM을 재프로비져닝하고 와이파이 통화를 활성화시켜줄 수 있는지 물어봤습니다.&lt;/p&gt;
&lt;p&gt;몇 번 연락한 결과, 다음과 같은 이메일을 받았습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Visible 이메일&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1928&quot; height=&quot;744&quot; src=&quot;/_astro/visible-email.Ctp4T7H-_Z10GawC.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;역시나 IMEI값을 조회해보니, 블랙리스트 상태로 떴습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;IMEI 조회&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;966&quot; height=&quot;809&quot; src=&quot;/_astro/imei-lookup.oKspDboF_Ze02qC.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;당연히도 판매자는 Swappa 상에서 자취를 감췄고, 전 즉시 PayPal에 클레임을 열었습니다. 근데 클레임을 확인하는데 PayPal이 하도 시간을 많이 끌어서 가끔씩은 판매자와 한통속인지 할 때도 있었죠. Swappa 상에서는 한번도 답변을 주지 않았지만, PayPal 클레임 상에선 “판매 당시 폰이 블랙리스트 처리되어 있지 않았다”(&lt;del&gt;그래서?&lt;/del&gt;)와 같은 이상한 “증거”를 올리고 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;PayPal 상 판매자 답변&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;511&quot; height=&quot;160&quot; src=&quot;/_astro/seller-response-paypal.Dvz4bVoZ_E3SMs.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이렇게 한 달간 계속 증거만 올리다가, PayPal이 결국 내놓은 해결방안은 제가 공식 경찰 진술서를 제공하면 환불을 해주겠다고 했습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;PayPal 경찰 진술서 요구&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;862&quot; height=&quot;679&quot; src=&quot;/_astro/paypal-case-timeline-and-police-report-request.CRSzC9qp_Z1TTEpC.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;문제는 전 아직도 한국에 있었고, 폰이 블랙리스트 당하는 바람에 전화도 걸 수 없었습니다. 설상가상으로 Visible은 미국 밖에선 eSIM 재다운로드조차 허용하지 않고요.&lt;/p&gt;
&lt;p&gt;일단 휴대폰 번호를 미국 밖에서 eSIM 재다운로드를 허용하고 와이파이 통화를 지원하는 통신사로 번호이동했습니다 (이게 왜 기본이 아닌지 모르겠습니다). 그 다음 보안관 사무실에 전화를 걸어 진술서를 작성할 수 있는 보안관을 요청했습니다. 미국 독립기념일인데도 보고서를 작성해서 보내주신 Tippecanoe 카운티 보안관님께 감사의 말씀 드립니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;경찰 보고서&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1472&quot; height=&quot;512&quot; src=&quot;/_astro/police-report.6hq118kh_Z1qCWqj.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;결국, PayPal에 클레임을 연 지 한 달만에 PayPal이 클레임을 제 편으로 종료시키고 전액을 환불해줬습니다.&lt;/p&gt;
&lt;h1 id=&quot;블랙리스트-원인&quot;&gt;블랙리스트 원인?&lt;/h1&gt;
&lt;p&gt;그럼 어떻게 판매 후 5개월 있다가 폰이 블랙리스트에 올라갔는지, 어떻게 이를 예방할 수 있는지 한번 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;PayPal 증빙 서류 제출이 한창일 때, Google Fi에 연락을 취해서 블랙리스트된 폰에 대해 물어봤는데, 다음과 같은 답변을 받았습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Google Fi 지원 대답&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1582&quot; height=&quot;875&quot; src=&quot;/_astro/google-fi-support-response.DNqYwYE7_Z168oWV.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;즉, 사기 방식은 다음과 같습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사기꾼은 Google Fi에서 새 폰을 사고, 휴대폰 보험에 가입합니다&lt;/li&gt;
&lt;li&gt;사기꾼은 이 폰을 페이스북 중고장터나 Swappa와 같은 중고장터에 올립니다&lt;/li&gt;
&lt;li&gt;중고 거래가 성사된 후, 사기꾼은 뜸을 살짝 들여 구매자가 폰에 문제가 없다고 믿게 만들죠&lt;/li&gt;
&lt;li&gt;이후 휴대폰 분실/도난 신고를 진행합니다&lt;/li&gt;
&lt;li&gt;보험 회사에서는 사기꾼에게 (사기를 반복할 수 있도록) 폰을 새로 줍니다&lt;/li&gt;
&lt;li&gt;동시에 폰을 블랙리스트 처리하고, 중고 구매자가 피해를 받죠&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;꽤 영리한 수법인데, PayPal류의 구매 시스템을 사용하지 않았고 구매 보장 제도같은 게 없다면, 이 시점에서는 사기꾼을 소액 청구 법원에서 고소하지 않는 이상 대책이 없기 때문입니다. 저도 거의 이 시점에 가까웠는데, 제 클레임이 끝난 후 일주일 후에 PayPal 구매 보장 제도가 종료됐거든요. 만약 사기꾼이 조금만 더 뜸을 들였다면 아마도 환불을 못 받았을 것 같습니다.&lt;/p&gt;
&lt;p&gt;사기꾼 입장에선 단점이 없는 사기입니다. 만약 성공하면 거의 100만원을 갖게 되고 다음 사기를 위한 새 폰을 받게 되죠. 만약 (제 경우처럼) 실패한다면, UPS 배송비만 잃고 (만약 PayPal이 청구한다면) 판매자 환불 수수료만 내면 되기 때문입니다. 아마도 그래서 다른 사람들도 이 사기에 당한 것 같습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;사기당한 구매자 리뷰 1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2159&quot; height=&quot;765&quot; src=&quot;/_astro/review-1.BaOUw4Wq_23r9P7.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;사기당한 구매자 리뷰 2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;997&quot; height=&quot;488&quot; src=&quot;/_astro/review-2.B9UyH_S2_Z1Xes8p.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;안타깝게도 중고폰을 구입하는 이상 이런 사기를 피하는 것은 꽤 어려운 것 같습니다. 전 개인적으로 이 사건 이후로 판매자가 신뢰하는 지인이 아니면 아마도 다시는 중고폰을 사지 않을 겁니다. 휴대폰 보험은 원 구매자가 매달 보험금을 지급하는 동안 계속 이어지니, 언제든지 블랙리스트될 수 있거든요.&lt;/p&gt;
&lt;p&gt;해외에 나와 있는 동안 미국 전화번호를 살려내는데 들인 고생만 생각해봐도 두번 다시는 하고 싶지 않습니다. 똑같이 고생하시지 마시고, 그냥 돈을 조금 더 들여서 제대로 된 새 폰을 구매하세요.&lt;/p&gt;
&lt;h1 id=&quot;수정&quot;&gt;수정&lt;/h1&gt;
&lt;p&gt;PayPal에서 영구 차단 조치를 내려버렸습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;PayPal 영구 차단 조치&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;572&quot; height=&quot;520&quot; src=&quot;/_astro/paypal-ban-notice.ByZUIFXj_2cqoua.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;처음에는 미국 PayPal 계정을 해외에서 접근한다는 명목으로 설명했는데 (아니 한국에 나와있는데 당연한 거 아닌가요?) 그러다가 제가 방학에 잠시 나왔다고 설명하니 더 이상 답변을 주지 않았습니다. 아마도 구매 5개월 이후에 연 클레임 때문에 강제 차단된 것 같습니다.&lt;/p&gt;
&lt;p&gt;혹시 소명 절차가 있는지 물어봤는데, 답변으로 거절되었고 결정은 변하지 않는다고 안내받았습니다. 참 쪼잔한 회사네요.&lt;/p&gt;</content:encoded></item><item><title>DaVinci Resolve에서의 짧은 코덱 테스트</title><link>https://ericswpark.com/ko/blog/2024/2024-06-28-davinci-resolve-short-codec-testing/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-06-28-davinci-resolve-short-codec-testing/</guid><pubDate>Fri, 28 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;수정: 아래 내용은 DaVinci Resolve의 무료 버전에만 해당됩니다. 글을 작성한 후 (라이센스된) Studio 버전은 10비트 색영역까지 전부 지원한다는 점을 알게 되었고, 아래에 있는 코덱들을 문제없이 불러오고 재생할 수 있는지 확인할 수 있었습니다. 보존 목적(그리고 무료 버전 사용자들)을 위해 이 블로그 글을 남겨 둡니다만, 만약 10비트 영상을 편집할 필요가 자주 있으시면 Studio 라이센스를 구매하시는 것을 추천드립니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;이번에 작업하고 있는 프로젝트를 위해 ZV-E1에서 찍은 파일들이 전부 DaVinci Resolve 불러오기에 실패하여 프로젝트를 망쳤는데, 오늘 한번 카메라 상의 둥영상 포맷 설정값들을 전부 테스트해봤습니다. 불러오기에 실패한 영상파일들은 프로그램 상에서 오디오 파일로 나타났죠.&lt;/p&gt;
&lt;p&gt;DaVinci Resolve 18.6 버전 기준 테스트 결과입니다:&lt;/p&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;파일 포맷 (fps)&lt;/th&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;XAVC HS 4K (24 fps)&lt;/td&gt;&lt;td&gt;100M / 4:2:2 / 10 bit&lt;/td&gt;&lt;td&gt;실패&lt;/td&gt;&lt;td&gt;오디오 파일로 보임&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;XAVC HS 4K (24 fps)&lt;/td&gt;&lt;td&gt;100M / 4:2:0 / 10 bit&lt;/td&gt;&lt;td&gt;성공*&lt;/td&gt;&lt;td&gt;처음 몇 초 깨짐&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;XAVC HS 4K (24 fps)&lt;/td&gt;&lt;td&gt;50M / 4:2:2 / 10 bit&lt;/td&gt;&lt;td&gt;실패&lt;/td&gt;&lt;td&gt;오디오 파일로 보임&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;XAVC HS 4K (24 fps)&lt;/td&gt;&lt;td&gt;50M / 4:2:0 / 10 bit&lt;/td&gt;&lt;td&gt;성공*&lt;/td&gt;&lt;td&gt;처음 몇 초 깨짐&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;XAVC S 4K (30 fps)&lt;/td&gt;&lt;td&gt;140M / 4:2:2 / 10 bit&lt;/td&gt;&lt;td&gt;실패&lt;/td&gt;&lt;td&gt;오디오 파일로 보임&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;XAVC S 4K (30 fps)&lt;/td&gt;&lt;td&gt;100M / 4:2:0 / 8 bit&lt;/td&gt;&lt;td&gt;성공&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;XAVC S 4K (30 fps)&lt;/td&gt;&lt;td&gt;60M / 4:2:0 / 8 bit&lt;/td&gt;&lt;td&gt;성공&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;참고:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;제가 가지고 있는 SD카드나 마이크로SD카드 전부 속도가 충분치 않아서 (V90 필요), XAVC S-I 옵션값들은 테스트를 아예 못해봤습니다 :/&lt;/li&gt;
&lt;li&gt;불러오기에 성공했지만 별표가 달려 있는 항목들은 신기하게 둥영상 처음 몇 초가 깨지면서, 둥영상 프레임에 색상 밴딩 효과가 나타났습니다:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;corrupt-frames&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1261&quot; height=&quot;796&quot; src=&quot;/_astro/corrupt-frames.CF-eL5NG_Z1DXRpH.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;처음에는 마이크로SD카드 문제인줄 알고 다른 카드로 다시 한번 테스트해봤는데, 똑같은 문제를 재현했습니다. 그리고 MPC-HC에서 재생을 했을땐 깨짐 문제가 발견되지 않았습니다.&lt;/p&gt;
&lt;h1 id=&quot;요약&quot;&gt;요약&lt;/h1&gt;
&lt;p&gt;DaVinci Resolve 18.6 무료 버전 기준으로 카메라 상 (만약 소니 ZV-E1을 사용중이라면) 다음 설정이 권장됩니다:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;XAVC S 4K, 100M / 4:2:0 / 8 bit&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;만약 4:2:2 크로마 서브샘플링을 사용하시면 둥영상이 오디오 파일로 불러오기됩니다.&lt;/li&gt;
&lt;li&gt;만약 10 bit를 사용하시면 처음 몇 초가 깨지고, 둥영상을 내보내기하실 때 다른 렌더링 문제가 있을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그리고 아직도 구매하는데 노예제도 약정을 서명하거나 (ㅋㅋ프리미엌ㅋ) 10초마다 크래시하거나 (Kdenlive) 코덱 알레르기가 없는 NLE 둥영상 편집기를 찾고 있습니다. 만약 추천하실 프로그램이 있다면 마스토돈에서 태그해서 알려주세요!&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;what-color-do-you-want-your-nle-editor-meme&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;489&quot; height=&quot;492&quot; src=&quot;/_astro/meme.C3mmG6RO_NXyVc.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>매일 쓰는 데스크탑에 이틀 정도 리눅스를 써봤습니다</title><link>https://ericswpark.com/ko/blog/2024/2024-06-06-linux-on-daily-desktop-attempt-lasted-two-days/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-06-06-linux-on-daily-desktop-attempt-lasted-two-days/</guid><pubDate>Thu, 06 Jun 2024 06:43:38 GMT</pubDate><content:encoded>&lt;p&gt;이번에는 뭐가 문제였는지 간단하게 정리하면요:&lt;/p&gt;
&lt;h1 id=&quot;nvidia-그래픽&quot;&gt;Nvidia 그래픽&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;언제나&lt;/em&gt; Nvidia 그래픽 탓인걸 알지만, 솔직히 이번에는 꽤 괜찮았습니다! 단 KDE와 Wayland 조합에서 다음과 같은 반짝이는 버그가 계속 발생했죠:&lt;/p&gt;
&lt;iframe src=&quot;https://tilde.zone/@ericswpark/112564801545814325/embed&quot; class=&quot;mastodon-embed max-w-full border-0 mx-auto&quot; width=&quot;800&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt; &lt;script src=&quot;https://tilde.zone/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
&lt;p&gt;다른 글을 읽어보니, Nvidia 드라이버가 Wayland 상에서 “명시적 동기화 (explicit sync)“를 지원하지 않아서 발생하는 문제인 듯 합니다. 드라이버 버전 555에 드디어 추가되었지만, 드라이버가 아직도 베타 상태이고 현재 정식 버전은 550 밖에 없거든요. (그리고 이 와중에 윈도우에서는 555.x 드라이버들이 출시된 지 한참 됐죠..)&lt;/p&gt;
&lt;p&gt;일시적인 해결책으로 X11을 사용하는 방법이 있는데, 이 버그는 해결하지만 성능이 꽤 저하됩니다.&lt;/p&gt;
&lt;h1 id=&quot;키보드-입력기&quot;&gt;키보드 입력기&lt;/h1&gt;
&lt;p&gt;다음으로 한국어 입력이 가능하도록 nimf를 설치했는데, &lt;code&gt;.xprofile&lt;/code&gt;을 작성하는 과정에서 실수를 했는지 데스크탑 환경 자체가 크래시해렸습니다:&lt;/p&gt;
&lt;iframe src=&quot;https://tilde.zone/@ericswpark/112566980149788228/embed&quot; class=&quot;mastodon-embed max-w-full border-0 mx-auto&quot; width=&quot;800&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt; &lt;script src=&quot;https://tilde.zone/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
&lt;p&gt;여담: 아직도 리눅스에서 키보드 입력기를 설정하는 것 자체가 꽤 어렵습니다. Wayland를 사용하는 다른 리눅스 머신들을 사용할 때 좋은 경험이 있었기에 &lt;code&gt;nimf&lt;/code&gt;를 선택했는데, 절차가 조금 개선이 필요해 보입니다. 지금 절차는 &lt;a href=&quot;https://wiki.archlinux.org/title/Nimf#Initial_setup&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;아치 리눅스 위키에서 설명하는 대로 설정값들을 &lt;code&gt;.xprofile&lt;/code&gt;&lt;/a&gt;에 추가한 다음, 로그아웃했다고 로그인하고, 그래도 안돼서 재부팅해보고, 그것도 안돼서 KDE 키보드 설정도 건드려 보고, 마지막에 &lt;code&gt;nimf &amp;amp;&lt;/code&gt;를 여러 번 실행시켜 가까스로 작동하게 만드는 방식이죠.&lt;/p&gt;
&lt;p&gt;위에 나와있는 방식보단, 데스크탑 환경의 설정 창에서 변경할 수 있는 설정값 하나였으면 좋겠습니다. 로그아웃하거나 재부팅을 할 필요가 없는 방법이요.&lt;/p&gt;
&lt;p&gt;어쨌든 이번에 이틀동안 매일 사용하는 데스크탑에서의 리눅스 실험을 마치겠습니다. 555 드라이버가 출시되면 한번 더 도전해보죠.&lt;/p&gt;</content:encoded></item><item><title>망해가는 패스키</title><link>https://ericswpark.com/ko/blog/2024/2024-04-26-passkey-fail/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-04-26-passkey-fail/</guid><pubDate>Fri, 26 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;(참고: 인용하는 글은 번역 오류로 의미가 변경될까봐 원문 그대로 가져왔습니다.)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://fy.blackhats.net.au/blog/2024-04-26-passkeys-a-shattered-dream/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;William Brown의 글&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;However Chrome simply never implemented it leading to it being removed. And it was removed because Chrome never implemented it. As a result, if Chrome doesn’t like something in the specification they can just veto it without consequence.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;전 브라우저 지원을 기다리는 걸 싫어합니다. 몇 년 전, 가족 전체에게 하드웨어 키를 나눠주면서 온라인 사이트들에 로그인할 때 사용하도록 전환했는데, 진짜 최악으로 끔찍한 경험이었죠. 제가 처음으로 하드웨어 키를 사용할때는, iOS의 사파리는 FIDO 키를 지원조차 하지 않았습니다 (아마도 iOS 13이나 14 이전이겠죠?) 그리고 결국 지원을 추가했을 때 NFC 키나 그런 것들만 지원해줘가지고 블루투스나 USB 연결 방식의 키들은 구글의 특수 지원 어플을 깔고 무슨 페어링 절차를 거치지 않았다면 아예 동작하지 않았습니다.&lt;/p&gt;
&lt;p&gt;패스키 도입이 위에 경험을 다시 반복하는 느낌입니다. 비트워든 (Bitwarden) 지원은 데스크탑 브라우저 확장 플러그인에만 그치죠. (참고로 2024년 현재까지도 브라우저 플러그인 외 다른 클라이언트 어플에서는 하드웨어 키조차 지원하지 않는다는 점 얘기했었나요? 이 어플들에 로그인하려면 결국 다른 2FA 방식을 추가해야 되는데, 그럼 계정 보안이 전체적으로 약해지는데도요?) 만약 패스워드 매니저에 패스키들을 저장할 수 없다면, 기기간 동기화가 되지 않고, 사이트들에 로그인할 &lt;strong&gt;때마다&lt;/strong&gt; 한 기기를 계속 꺼내고 싶지는 않습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Both Chrome and Safari will try to force you into using either hybrid (caBLE) where you scan a QR code with your phone to authenticate - you have to click through menus to use a security key. caBLE is not even a good experience, taking more than 60 seconds work in most cases. The UI is beyond obnoxious at this point. Sometimes I think the password game has a better ux.&lt;/p&gt;
&lt;p&gt;The more egregious offender is Android, which won’t even activate your security key if the website sends the set of options that are needed for Passkeys. This means the IDP gets to choose what device you enroll without your input. And of course, all the developer examples only show you the options to activate “Google Passkeys stored in Google Password Manager”. After all, why would you want to use anything else?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;아니, 왜 아이클라우드 계정에 패스키를 저장하지 않냐고요? 왜냐하면 쿠퍼티노에서 만들지 않은 기기들도 보유하고 있고, 이 기기들은 애플의 서버에 접근조차 하지 못하기 때문이죠. 그리고 안드로이드 상에서는 원글에 보시다시피 구글이 패스키들을 전부 차지할려고 하고요.&lt;/p&gt;
&lt;p&gt;결국 패스키는 복잡함이 가미된 SSO에 불과하다고 봅니다.&lt;/p&gt;</content:encoded></item><item><title>한국 통신사들은 틀림없이 거꾸로 진화할 겁니다</title><link>https://ericswpark.com/ko/blog/2024/2024-04-06-koreas-mobile-carriers-are-evolving-backwards/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-04-06-koreas-mobile-carriers-are-evolving-backwards/</guid><pubDate>Sat, 06 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;미국에서 지난 1년 간 생활했기에 한번 한국과 미국 통신사들을 비교하면서 왜 한국 통신사들이 끔찍한지 설명해보겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;번호-명의-섞기-금지&quot;&gt;번호 명의 섞기 금지&lt;/h1&gt;
&lt;p&gt;한국에 eSIM이 도입되는데 한참이 걸렸지만, 마침내 도입되었을 때 여러 제약이 뒤따라왔습니다. 가장 큰 단점은 &lt;strong&gt;한 휴대폰에서 명의가 다른 심카드 2개를 사용할 경우 두 회선이 모두 정지된다는 것이죠.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;만약 부모님 휴대폰에 신호가 안 잡힐 때 휴대폰 문제인지 심카드 문제인지 확인해보려고 잠시동안 심카드를 본인 휴대전화에 넣어도 바로 두 회선이 모두 정지되고, 다시 활성화하려면 가까운 통신사 지점을 찾아가서 시간을 낭비해야 합니다.&lt;/p&gt;
&lt;p&gt;(참고로 한국에서는 듀얼심 단말기들이 그렇게 많지 않았기 때문에 이 제약이 문제가 되지 않았는데, 아마도 듀열심 단말기들이 부진했던 이유 중 하나가 통신사의 입김이 강해서 그렇지 않았나 싶습니다. 물론 eSIM이 도입된 이후로는 위에 방식대로 심카드를 섞는 게 가능해졌죠.)&lt;/p&gt;
&lt;p&gt;빌미는 대포폰을 방지하기 위해서라고 하지만, 실제로 물리심을 사용할 때도 대포폰이 문제가 되었는데 얼마나 효력이 있지 싶습니다.&lt;/p&gt;
&lt;h1 id=&quot;imei-수집&quot;&gt;IMEI 수집&lt;/h1&gt;
&lt;p&gt;위의 제약사항을 강제하기 위해, 한국 통신사들은 eSIM을 발급할 때 휴대전화의 IMEI값 2개를 전부 수집해갑니다. 이 정보를 사용하여 단말기에 들어간 심카드들이 전부 한 명의로만 개통되어 있는지 확인하죠.&lt;/p&gt;
&lt;p&gt;말도 안되는 게, 이 세상에 한국을 제외하고 IMEI 값을 전부 가져가는 통신사는 하나도 없습니다. 미국에서 eSIM을 발급받을때 IMEI 값 하나를 Visible (미국 통신사 버라이즌의 MVNO 통신사) 어플에 입력하는게 전부였습니다.&lt;/p&gt;
&lt;p&gt;eSIM 발급 얘기가 나와서 말인데요…&lt;/p&gt;
&lt;h1 id=&quot;한국에서-esim-발급받는거-너무한거-아니에요&quot;&gt;한국에서 eSIM 발급받는거, 너무한거 아니에요?&lt;/h1&gt;
&lt;p&gt;한국에서 eSIM을 재발급받으려면 매번 통신사에 2,750원을 지급해야 합니다. 미국에서 아무런 제약 없이 eSIM을 옮기는 것을 허용하는 제 통신사(Visible)와 비교하면 너무나도 대조되죠. 따지고 보면 이 세상에서 한국 빼고 eSIM 재발급 비용을 청구하는 나라 하나 없습니다.&lt;/p&gt;
&lt;p&gt;eSIM 존재 이유 자체가 다 디지털 방식이기에 실제 물리 심카드를 생산하는 비용이 없고, 서버에서 몇 비트만 바꾸고 변경된 eSIM 정보를 전송하는데 돈이 설마 그만큼 들어갈까요?&lt;/p&gt;
&lt;p&gt;한국 통신사 입장은 재발급 비용이 eSIM 기술에 관련된 로얄티 때문이라고 합니다. 하지만 다른 통신사들은 이 금액을 가입자들에게 부과하지 않는다는 점, 그리고 이미 엄청나게 비싼 통신비를 지불한다는 점을 고려하면 그냥 욕심 좀 버리고 금액을 통신사측이 부담하는게 옳다고 봅니다.&lt;/p&gt;
&lt;p&gt;아, 그리고 eSIM을 실제로 다시 받는 과정을 한번 볼까요?&lt;/p&gt;
&lt;p&gt;미국에서는 eSIM을 재발급받기 위해선 어플에서 몇번만 누르면 재발급이 완료됩니다.&lt;/p&gt;
&lt;p&gt;한국에서는요. 평일 업무 시간 중 통신사 고객센터에 전화하고 다운로드 절차가 &lt;em&gt;제발&lt;/em&gt; 아무런 문제 없이 동작하기를 빌거나, 직접 통신사 영업점을 방문해야 합니다. 저처럼 지구 반대편에 있다면 하기 꽤 힘든 일이죠.&lt;/p&gt;
&lt;h1 id=&quot;의미없는-4g5g-구분&quot;&gt;의미없는 4G/5G 구분&lt;/h1&gt;
&lt;p&gt;미국(과 다른 여러 나라)에서는 통신 기술 세대 구분이 없습니다. 만약 새로운 5G 폰을 구매한 다음에 기존 4G 폰에서 심을 이식하면 바로 5G 통신을 사용할 수 있죠.&lt;/p&gt;
&lt;p&gt;한국에서는 4G와 5G과 구분되어 있기에 가입시 이걸 요금제로 선택을 해야 합니다. 물론, 5G로 가입하면 가격이 더 비싸지죠.&lt;/p&gt;
&lt;p&gt;문제는, 가입자들은 5G가 4G보다 그렇게 더 많은 장점이 없기에 그냥 4G를 쓰는데 만족하게 된다는 점입니다. (인프라가 완벽하게 구축되지 않은 초기 단계이기 때문이죠.) 그러면 통신사들은 통신 장비를 증설하는데 돈을 들이지 않고, 결과적으로 계속 4G와 5G를 병행해서 사용하게 되죠.&lt;/p&gt;
&lt;p&gt;하지만 다른 국가에서는 시작부터 모든 가입자들을 5G로 넘겼기 때문에, 5G 관련 장비를 증설할 명분이 더 많다는 게 핵심입니다. 그럼 사용되지 않는 낡은 장비를 철거하고 3G와 4G 서비스를 종료할 수 있게 되죠.&lt;/p&gt;
&lt;p&gt;장기적으로 봤을 때, 이렇게 이윤 추구만을 이유로 통신 세대를 구별한다면 한국은 다른 나라에 비해 통신망 품질이 떨어질 수 밖에 없습니다.&lt;/p&gt;
&lt;p&gt;품질 떨어지는 얘기가 나와서 말인데요. 한국에는 없는 통신 기능도 하나 소개해드릴까요?&lt;/p&gt;
&lt;h1 id=&quot;wi-fi-전화의-부재&quot;&gt;Wi-Fi 전화(의 부재)&lt;/h1&gt;
&lt;p&gt;만약 통신이 원할하지 않은 장소에서 Wi-Fi가 있다면 Wi-Fi를 통해 전화할 수 있음 좋지 않을까요? 아이러니하게도, 한국에서는 널려있는 게 공공 Wi-Fi지만, 정작 그걸 통해 전화는 할 수 없습니다.&lt;/p&gt;
&lt;p&gt;예전에 한국에서 지방 쪽에서 여행 중 이것 때문에 꽤 짜증났던 적이 있습니다. 통신 신호는 없지만 꽤 좋은 Wi-Fi 연결이 있음에도 불구하고, 도심 쪽으로 이동해야 전화를 걸거나 문자를 보낼 수 있었죠.&lt;/p&gt;
&lt;p&gt;이건 이미 선진국 통신사들은 오래 전부터 지원하는 기능인데, 왜 한국만 도입을 하지 않았는지 이해가 되질 않습니다. 비용 문제 때문이 아닐지 추정만 할 수 밖에 없죠.&lt;/p&gt;
&lt;p&gt;비용 얘기가 나온 김에 금액 한번 비교하고 마치겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;무제한의-비용&quot;&gt;무제한의 비용&lt;/h1&gt;
&lt;p&gt;미국에서는 무제한 전화, 문자, 데이터(물론 5G죠!)를 사용하기 위해 25달러 (한화 약 33,000원)을 지불합니다. 테더링도 제약없이 완전 무료죠.&lt;/p&gt;
&lt;p&gt;한국에서는 비슷한 서비스를 위해 10만원 넘게 지불해야 합니다. 3배가 넘는 가격을 내도 테더링에 제약이 있죠.&lt;/p&gt;
&lt;p&gt;만약 아무것도 변하지 않는다면 이 추세가 계속될거라 예상합니다. 돈을 많이 내고도 돌아오는 건 적겠죠.&lt;/p&gt;</content:encoded></item><item><title>Lockdown Browser가 싫은 이유</title><link>https://ericswpark.com/ko/blog/2024/2024-02-20-i-hate-lockdown-browser/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-02-20-i-hate-lockdown-browser/</guid><pubDate>Tue, 20 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;최근들어 거의 대부분의 작업을 위해 리눅스 노트북을 사용하고 있습니다. 윈도우나 맥OS가 아닌, 터미널을 사용할 수 있다는 점이 꽤 재밌고 유용하기 때문이죠.&lt;/p&gt;
&lt;p&gt;그래서 궁금해진 점이, 제가 현재 듣고 있는 강의의 시험을 똑같은 노트북해서 치를 수 있는지, 노트북이 Respondus의 Lockdown Browser를 돌릴 수 있을지 궁금해졌습니다. 이 프로그램은 시험 전용 브라우저로, 시험을 치르는 동안 다른 프로그램들을 못 돌리게 막습니다.&lt;/p&gt;
&lt;p&gt;요약해서 정리하자면: 불가능합니다. 하지만 같이 확인해보고 싶다면 Lockdown Browser가 얼마나 끔찍한지, 그리고 요즘 학생들이 겪어야 하는 미친 짓거리를 구경할 수 있죠.&lt;/p&gt;
&lt;h1 id=&quot;리눅스-버전이-있나요&quot;&gt;리눅스 버전이 있나요?&lt;/h1&gt;
&lt;p&gt;물론 처음으로 확인해야죠. 없습니다.&lt;/p&gt;
&lt;h1 id=&quot;가상머신으로-돌아가나요&quot;&gt;가상머신으로 돌아가나요?&lt;/h1&gt;
&lt;p&gt;돌아갑니다. 이 화면을 보여주려 말이죠:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;lockdown-browser-lockout-part-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1563&quot; height=&quot;1016&quot; src=&quot;/_astro/lockdown-browser-lockout-part-1.ocOgNpri_Z27dX81.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;그리고 바로 종료됩니다.&lt;/p&gt;
&lt;p&gt;리눅스 덕후로서, 조금 더 깊이 파해쳐봅시다.&lt;/p&gt;
&lt;h1 id=&quot;lockdown-browser-속이기&quot;&gt;Lockdown Browser 속이기&lt;/h1&gt;
&lt;p&gt;LDB가 VM에서 돌아가는 사실을 LDB에게서 숨겨야 합니다.&lt;/p&gt;
&lt;p&gt;일단 호스트 머신의 SMBIOS 정보를 그대로 가상 머신으로 념겨야 합니다. 그럴려면 QEMU의 XML을 편집해야 되는데, &lt;code&gt;&amp;#x3C;os&gt; &amp;#x3C;/os&gt;&lt;/code&gt; XML 태그 사이에 다음 줄들을 삽입합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;os&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &amp;#x3C;!-- Some other lines... --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &amp;#x3C;smbios mode=&quot;host&quot; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/os&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다음으로, CPU 정보도 변경해야 합니다. 기본적으로, QEMU는 가상 머신에게 가상화 아래 돌아가고 있다고 보고하고, vCPU가 실제 CPU가 아니라고 보고합니다. QEMU가 너무 정직하게 이러지 않도록 하고, CPU 정보를 그대로 넘기라고 지시해야 하죠. &lt;code&gt;&amp;#x3C;cpu&gt;&lt;/code&gt; 태그를 찾은 다음, 닫혀 있을 경우 닫는 태그를 새로 생성한 후 중간에 다음 줄들을 붙여넣습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;cpu ...&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;topology sockets=&quot;1&quot; dies=&quot;1&quot; cores=&quot;1&quot; threads=&quot;2&quot;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;feature policy=&quot;disable&quot; name=&quot;hypervisor&quot;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/cpu&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때 CPU 토폴로지를 vCPU 설정값에 알맞게 변경해야 합니다. 만약 잘못된 설정값이 입력되었을 경우, 적용되지 않거나 VM이 크래시할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이렇게 하이퍼바이저를 비활성화시켜주면, VM에게 실제로는 가상화되고 있지 않다고 거짓말을 하는 셈이 됩니다. 그럼 VM은 CPU가 가상화 능력이 있다고 가정하고, 가상화되고 있다고 생각하지 않죠. 게스트 OS 내부의 작업 관리자로 확인해보세요; 만약 “가상 머신: 사용”으로 표시된다면 설정이 제대로 적용되지 않은 사례입니다. 제대로 적용되면 “가상화: 사용”으로 표시되어야 합니다.&lt;/p&gt;
&lt;p&gt;마지막으로, LDB에게서 감춰야 하는 것은 기기 명칭입니다. 가상 머신 전원을 킨 다음, &lt;code&gt;regedit&lt;/code&gt; 안으로 들어가, 다음 경로로 이동합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\SCSI&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 가상 드라이브의 &lt;code&gt;FriendlyName&lt;/code&gt;을 &lt;code&gt;Samsung HDD 500 GB ATA&lt;/code&gt;나 &lt;code&gt;LG SuperDrive 20x CD-ROM&lt;/code&gt;처럼 변경해줍니다. (만약 &lt;code&gt;regedit&lt;/code&gt;이 경고 메시지를 띄우면 권한을 새로 추가해줘야 할 수도 있습니다.)&lt;/p&gt;
&lt;p&gt;이렇게 설정값들을 모두 변경해주면, LDB가 이제 실행될 겁니다.&lt;/p&gt;
&lt;p&gt;잠시동안요.&lt;/p&gt;
&lt;h1 id=&quot;2단계-환경-확인&quot;&gt;2단계 환경 확인?&lt;/h1&gt;
&lt;p&gt;아쉽게도, LDB는 시험이 시작된 후 한번 더 환경을 확인해서 사용자가 컴퓨터가 시험을 치를 수 있다고 믿게 만듭니다. 시험을 시작하면, 몇 초 후에 다음과 같은 화면이 표시됩니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;lockdown-browser-lockout-part-2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1553&quot; height=&quot;1002&quot; src=&quot;/_astro/lockdown-browser-lockout-part-2.CO2gTHqt_Job8w.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;아마도 다른 가상 머신 탐지 방법을 사용하는 듯합니다. 만약 궁금하다면, &lt;a href=&quot;https://github.com/a0rtega/pafish&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;pafish라는 레포지토리에서&lt;/a&gt; 몇몇 탐지 방법을 확인해볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;아마도 시간이 넉넉하다면 이 탐지 기법도 우회할 방법을 찾을 수 있을 듯 합니다. 하지만 시험은 다음 주에 치뤄지고, 솔직히 확인하고 싶은 마음도 없으며, 실제 시험에서 이 문제가 발생한다면 리눅스 노트북에서 시험을 치르고 싶었다고 설명해도 부정행위로 간주할 가능성 때문에 그냥 다른 컴퓨터로 시험을 볼 듯 합니다.&lt;/p&gt;
&lt;p&gt;물론, 그 마지막 문장에서 문제가 보이겠죠.&lt;/p&gt;
&lt;h1 id=&quot;lockdown-browser가-도대체-뭘-방지하나요&quot;&gt;Lockdown Browser가 도대체 뭘 방지하나요?&lt;/h1&gt;
&lt;p&gt;Lockdown Browser는 부정행위를 방지하고자 시험을 치루고 있는 컴퓨터를 잠궈 다른 프로그램들 (예를 들어, 다른 브라우저)를 실행할 수 없게 만듭니다.&lt;/p&gt;
&lt;p&gt;물론, 이는 시험을 치루는 컴퓨터&lt;strong&gt;만&lt;/strong&gt;으로 한정되죠.&lt;/p&gt;
&lt;p&gt;그럼 학생들이… 컴퓨터 2대를 사용한다면? 아니, 휴대폰을 사용한다면? 모두 다 휴대폰 한 대씩은 있죠? &lt;a href=&quot;https://youtu.be/VGByCvWDINA?t=20&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;del&gt;님폰없?&lt;/del&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;저희 교수님은 실제로 학생들에게 만약 노트 자료가 노트북 상에 있을 경우 (오픈노트 시험), 캠퍼스의 컴퓨터를 사용하여 시험을 치르도록 권장했습니다. 그럼 Lockdown Browser를 사용하는 이유가 뭐죠?&lt;/p&gt;
&lt;h1 id=&quot;그리고-lockdown-browser를-설치하면-안되는-이유-몇-가지&quot;&gt;그리고 Lockdown Browser를 설치하면 안되는 이유 몇 가지…&lt;/h1&gt;
&lt;p&gt;가상 머신으로 돌리는 방식을 테스트하던 도중, Lockdown Browser를 돌리는 가상화된 OS가 비정상적으로 동작하도록 하는 것을 확인했습니다. 한 때는, 작업 관리자가 갑자기 비활성화되었는데, Lockdown Browser가 열려있지 않음에도 작업 관리자를 열 수 없었습니다. 가상 머신 스냅샷을 복원하여 이전 상태로 돌아가 그제서야 다시 모든 것이 정상적으로 작동했죠.&lt;/p&gt;
&lt;p&gt;그게 전부가 아닙니다. 만약 &lt;a href=&quot;https://www.google.com/search?q=lockdown+browser+broke+my+computer+site%3Areddit.com&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;“Lockdown Browser broke my computer”&lt;/a&gt;를 검색해본다면, 레딧에 여러 경험담을 확인해볼 수도 있죠:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/KSU/comments/sq1qrr/lockdown_browser_crashed_my_dell_desktop/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://www.reddit.com/r/KSU/comments/sq1qrr/lockdown_browser_crashed_my_dell_desktop/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/jmu/comments/zqysg0/does_lockdown_browser_damage_anyone_elses_computer/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://www.reddit.com/r/jmu/comments/zqysg0/does_lockdown_browser_damage_anyone_elses_computer/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/utarlington/comments/jx6teg/respondus_lockdown_browser_keeps_messing_up_my/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://www.reddit.com/r/utarlington/comments/jx6teg/respondus_lockdown_browser_keeps_messing_up_my/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;별로 놀랍지도 않은게, Lockdown Browser는 다른 프로그램이 열리는 것을 방지하고, 컴퓨터 전체 통제 권한을 가져가며, 슬립 모드로 진입하는 것을 막기 위해 윈도우의 그룹 정책, 레지스트리, 그리고 전원 옵션들을 건드립니다.
그리고 LDB의 코드가 발로 작성했는지 가끔씩 시험이 종료되었는데도 시험 전 상태로 복원해놓지 않는 경우도 있죠.&lt;/p&gt;
&lt;p&gt;학생들이 학기 도중에 컴퓨터를 포맷하고, 운영체제와 수업에 사용되는 모든 프로그램을 하나하나 재설치하는 것보다 할 만한 좋은 것들이 뭐가 있을까요? 없죠. 윈도우/맥OS 설정 화면을 반가워하는 학생들만 있을거라 확신합니다.&lt;/p&gt;
&lt;h1 id=&quot;다른-방안은요&quot;&gt;다른 방안은요?&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/gucci-on-fleek/lockdown-browser/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;윈도우 샌드박스를 사용하는 다른 프로젝트&lt;/a&gt;가 있는데, 예는 실제로 시험 종료까지 정상적으로 작동합니다. (아마도 윈도우 샌드박스는 하이퍼V (Hyper-V)를 사용해서 그런 것 같은데, 얘는 마이크로소프트가 기업 고객들과 Xbox 취약점을 막아내기 위해 적극적으로 투자해서 탐지하기 더 어려운 듯 합니다.) 히지만, &lt;a href=&quot;https://github.com/gucci-on-fleek/lockdown-browser/discussions/53&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;최근에 Respondus의 버그 트래커에 레포지터리가 추가되면서&lt;/a&gt;, 추후에는 사용할 수 없게 패치될 듯 합니다.&lt;/p&gt;
&lt;h1 id=&quot;그럼-어떻게-하죠&quot;&gt;그럼 어떻게 하죠?&lt;/h1&gt;
&lt;p&gt;이 블로그 글이 업로드되면, 교수님에게 링크를 보내서 Lockdown Browser의 사용이 재검토될 수 있도록 할 예정입니다. 만약 학교나 대학에서 Lockdown Browser를 사용한다면, 교수님이나 선생님께 이 블로그 글 링크를 보내 Lockdown Browser를 사용하지 &lt;em&gt;않도록&lt;/em&gt; 추천하는 것을 권장합니다.&lt;/p&gt;
&lt;p&gt;여기서 몇몇 분들은 제가 VM 탐지 기능 우회를 실패했기 때문에 LDB가 잘 동작하는 것이 아닌지 의문이 들 수 있습니다. 결론적으로 잘 동작하는 게 아닙니다. 이 글의 목적은 LDB가 필요 없음을, 학생들에게 골치아픈 문제만 하나 더 만들어주며 컴퓨터를 망가뜨리는 윈인이 될 수 있음을 보여주는 것입니다. 실제로 부정행위를 하는 학생들은 그냥 다른 기기들을 사용하면 LDB의 부정방지 시스템에 걸릴 위험도 전혀 없거든요. 다시 말해, LDB는 학생들을 차별하는데, 특히 여러 기기를 구매할 여력이 안되는 가난한 학생층을 더욱 더 차별하면서 부정행위를 하는 학생들은 그냥 하게 내버려둔다는 것입니다. (아마도 여기에 적절한 사회 논평 한 줄이 있을 것 같은데 지금 거의 자정이라 떠올리는게 너무 힘들고 귀찮아서 하나 상상해서 여기에 대체하시면 됩니다.)&lt;/p&gt;
&lt;p&gt;사용이 중지되는 그때까진, 도서관 컴퓨터 등에 이 멀웨어스러운 시험 프로그램을 깔아서 사용해야겠죠. 개인 컴퓨터에, 설사 리눅스에서 동작하더라도, 미쳤다고 설치하는 일은 절대로 없을테니까요.&lt;/p&gt;
&lt;h1 id=&quot;업데이트&quot;&gt;업데이트&lt;/h1&gt;
&lt;p&gt;오늘 교수님께서 답변을 보내주셨는데, 추후에 Lockdown Browser 요구사항이 삭제될 예정이라고 합니다! (교수님께서 지금 만약 이 글을 읽고 계신다면 감사합니다!)&lt;/p&gt;
&lt;p&gt;만약 기관에서 Lockdown Browser의 사용을 요구한다면, 이 블로그 글을 보내서 얼마나 쓸데없는 일인지 보여주는 것을 고려해보세요. 그리고 만약 기관이 정책을 변경한다면 어딘가에 경험담도 올려주시고요! 이 쓰레기 같은 소프트웨어에 대한 비판이 많아질수록 사용하지 &lt;strong&gt;않는 것&lt;/strong&gt;이 일반화되겠죠.&lt;/p&gt;</content:encoded></item><item><title>PowerShell 사생활 보호 모드</title><link>https://ericswpark.com/ko/blog/2024/2024-02-03-powershell-incognito-mode/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-02-03-powershell-incognito-mode/</guid><pubDate>Sat, 03 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;/ko/blog/2021/2021-11-13-terminal-incognito-mode&quot;&gt;2021년에 (벌써 3년 전이라고?!), bash 터미널에서 “사생활 보호 모드”를 활성화하는 방법에 대해 글을 쓴 적이 있습니다&lt;/a&gt;. 요즘에는 윈도우 시스템도 사용하게 되어, PowerShell의 명령 기록을 잠시 비활성화하는 방법이 필요하게 되었죠.&lt;/p&gt;
&lt;p&gt;PowerShell은 두 개의 명령 기록 모듈이 있습니다. 첫번째 모듈은 현재 세션으로만 한정되고, 그 세션에서 실행한 모든 명령 기록을 &lt;code&gt;Get-History&lt;/code&gt; 명령으로 확인할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;PS C:\Users\erics\OneDrive\Pictures&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Get-History&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  Id CommandLine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  --&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -----------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cd .\OneDrive\Pictures\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ls&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;나머지 명령 기록 모듈은 PSReadLine이 저장하는데, 이게 문제가 되는 이유가 &lt;code&gt;.bash_history&lt;/code&gt;처럼 (윈도우나 파일을 삭제할 때까지 보존되는) 파일에 명령 기록을 저장하기 때문입니다. 이 파일은 여기에서 찾아볼 수 있죠: &lt;code&gt;$env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;기본적으로, PowerShell쪽은 bash와 다르게 스페이스로 시작하는 명령도 기록에 저장해버리기 때문에 기본 설정값이 더 “열악하죠”.&lt;/p&gt;
&lt;p&gt;다행히도, 이러한 문제점을 해결하기 위해 프로필 스크립트를 수정하는 방법이 있습니다.&lt;/p&gt;
&lt;p&gt;(참고: 아래의 코드 대부분은 &lt;a href=&quot;https://github.com/PowerShell/PSReadLine/issues/2698&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;이 문제에 대한 GitHub issue에서 발췌해왔습니다&lt;/a&gt;!)&lt;/p&gt;
&lt;h2 id=&quot;실행-정책-변경하기&quot;&gt;실행 정책 변경하기&lt;/h2&gt;
&lt;p&gt;PowerShell은 기본으로 신뢰하지 않는 스크립트를 실행하지 않도록 설정되어 있기 때문에, 실행 정책을 변경해야 합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Set-ExecutionPolicy&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Scope CurrentUser &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ExecutionPolicy Unrestricted &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Force&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때 사용 가능한 정책은 &lt;code&gt;Unrestricted&lt;/code&gt;와 &lt;code&gt;Bypass&lt;/code&gt; 2개입니다. &lt;code&gt;Unrestricted&lt;/code&gt;는 스크립트를 실행하기 전 경고하고, &lt;code&gt;Bypass&lt;/code&gt;는 아무런 경고 없이 아무거나 집어 실행하죠 (리눅스 셸과 &lt;code&gt;bash&lt;/code&gt;가 &lt;code&gt;+x&lt;/code&gt; 플래그가 달려 있는 아무 스크립트나 실행하는 것처럼요).&lt;/p&gt;
&lt;h2 id=&quot;프로필-스크립트-수정하기&quot;&gt;프로필 스크립트 수정하기&lt;/h2&gt;
&lt;p&gt;즐겨쓰는 텍스트 편집기에서 프로필 스크립트를 열어줍니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;notepad&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; $profile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 다음을 붙여넣으시면 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 스페이스로 시작하는 명령 기록하지 않기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Set-PSReadLineOption&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;AddToHistoryHandler {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    param&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]$line)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $line.Length &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-gt&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -and&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $line[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-ne&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos; &apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -and&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $line[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-ne&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 명령 기록 비활성화/활성화 wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Disable-PSReadLineHistory&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    $&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;global&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:PSReadLineOldHistorySaveStyle &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Get-PSReadLineOption&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).HistorySaveStyle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    Set-PSReadLineOption&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HistorySaveStyle SaveNothing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Enable-PSReadLineHistory&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Only change the history style if we previously saved it in `Disable-PSReadLineHistory`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Test-Path&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; variable:PSReadLineOldHistorySaveStyle) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        Set-PSReadLineOption&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HistorySaveStyle $&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;global&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:PSReadLineOldHistorySaveStyle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 PowerShell의 명령 기록을 잠시 비활성화하고 싶을 때, &lt;code&gt;Disable-PSReadLineHistory&lt;/code&gt;를 실행하면 됩니다. 현재 세션에서만 명령 기록을 멈춰주죠.&lt;/p&gt;
&lt;p&gt;이게 기본으로 탑재되어 있었다면 얼마나 좋았을까요.&lt;/p&gt;
&lt;h2 id=&quot;예전-명령-기록-삭제하기&quot;&gt;예전 명령 기록 삭제하기&lt;/h2&gt;
&lt;p&gt;만약 예전에 실행했던 민감한 명령들이 기록에 남아있는 경우, &lt;code&gt;Clear-History&lt;/code&gt;를 실행한 후 위에 나와있는 PSReadLine의 명령 기록 파일을 삭제하여 없애버릴 수 있습니다.&lt;/p&gt;
&lt;p&gt;아니면, &lt;a href=&quot;https://stackoverflow.com/a/38807689/5202174&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Stack Overflow에 있는 이 스크립트를 사용하세요&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>AutoHotKey로 윈도우 블루투스 설정 열기</title><link>https://ericswpark.com/ko/blog/2024/2024-01-07-windows-bluetooth-shortcut-with-autohotkey/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2024/2024-01-07-windows-bluetooth-shortcut-with-autohotkey/</guid><pubDate>Sun, 07 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;윈도우 11에서 블루투스 설정을 빠르게 여는 AutoHotKey 스크립트입니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Persistent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#b::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; Open Quick Actions window&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Send(&amp;quot;{LWin Down}a{LWin Up}&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; The quick action window takes a while to initialize and allow keyboard navigation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; Without the sleep timer you may end up on the wrong screen (or disabling WiFi)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; If your computer is slower, increase this value a bit more&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Sleep(600)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; WiFi -&amp;gt; Bluetooth&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Send(&amp;quot;{Right}&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; Bluetooth chevron&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Send(&amp;quot;{Tab}&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; Enter Bluetooth chevron&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Send(&amp;quot;{Enter}&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://www.theverge.com/2022/2/25/22951225/microsoft-windows-11-bluetooth-quick-settings-insider-testing-build&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;이 스크립트를 사용하시려면 윈도우 11 빌드 22563 이상을 사용하셔야 합니다.&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>SKT에서 데이터쉐어링 eSIM을 발급받았습니다!</title><link>https://ericswpark.com/ko/blog/2023/2023-12-28-i-got-skt-to-make-me-a-data-sharing-esim/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2023/2023-12-28-i-got-skt-to-make-me-a-data-sharing-esim/</guid><pubDate>Thu, 28 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;원래 정책상 SKT에서 데이터쉐어링용으로 eSIM 발급은 불가능합니다. 홈페이지에서도 발급을 시도하면 태블릿 등의 스마트기기는 발급을 지원하지 않는다고 하고, 고객센터 상담원들도 불가능하다고 하거든요. 인터넷에서도 된다는 경험담이 전무하여 다시 유심으로 개통을 해야 하는 줄 알았습니다.&lt;/p&gt;
&lt;p&gt;근데 어제 SKT 대리점을 방문하여 발급을 시도하니, IMEI1과 IMEI2 값이 필요했습니다. 원래 휴대폰들은 통신 설비가 이렇게 eSIM과 유심 각각 한 IMEI가 할당되는 방식이라 eSIM 발급이 가능한데, 아이패드 같이 eSIM을 지원하는 태블릿들은 거의 대부분 IMEI 값을 eSIM과 유심하고 번갈아가며 사용하기 때문에 이 단계에서 대부분 포기합니다. 해결 방법은 그냥 똑같은 IMEI 값을 두 번 입력하면 개통이 정상적으로 진행됐습니다.&lt;/p&gt;
&lt;p&gt;다만, 이 방식도 정책상 허용되는 것은 아니고, 데이터쉐어링 회선은 툭하면 정지되거나 문제가 생길 수 있기 때문에 자칫 잘못하면 eSIM 발급비용을 날리실 수 있다는 가정하에 진행하셔야 됩니다.&lt;/p&gt;
&lt;p&gt;개통 과정이 끝나고 아이패드에서 eSIM QR 코드를 찍으니 정상적으로 eSIM이 다운되었습니다. 사실 이게 불가능한 것도 아닌게 원래 eSIM하고 유심하고 똑같은데, 한국 통신사에서만 이걸 다르게 취급하거든요. (&lt;del&gt;유별나게&lt;/del&gt;)&lt;/p&gt;
&lt;p&gt;만약 똑같이 시도해보는데 직원분이 안해줄려고 하면 다른 대리점이나 직영점에 가시는 걸 추천드립니다. 저도 약 1년 전에는 다른 대리점에서 퇴짜를 맞았거든요. 원래 정책상 안되는 건데 해주시는 거라 안 해준다고 화내시진 마시고 친절하게 물어보거나 다른 분께 요청하는 것이 더 수월합니다.&lt;/p&gt;
&lt;p&gt;이렇게 IMEI값 2개를 전부 필요로 하는 이유를 생각해봤는데, 한국에서는 eSIM과 유심의 명의가 일치하지 않는 경우 대포폰 방지를 목적으로 두 회선을 모두 정지시키는데, 이를 위해 IMEI 값을 전부 수집하는 듯 합니다. 말도 안되는 것이, 대포폰 이외에도 명의가 다른 SIM들을 장착해야 하는 상황들도 많고 (예시로 친척 휴대폰에 유심이 왜 인식이 안되는지 확인하는 경우), 만약 진짜 대포폰을 만들어야 된다면 그냥 단말기만 더 구하면 되거든요. 그냥 소비자들만 더 불편하게 하려는 정책이 아닌가 싶습니다.&lt;/p&gt;
&lt;h2 id=&quot;수정&quot;&gt;수정&lt;/h2&gt;
&lt;p&gt;2024년 5월 24일 기준 아직도 개통되는 거 직접 가서 확인했습니다.&lt;/p&gt;</content:encoded></item><item><title>Docker와 YOURLS로 나만의 단축 URL 호스팅하기</title><link>https://ericswpark.com/ko/blog/2023/2023-11-29-hosting-my-own-short-urls-with-yourls-and-docker/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2023/2023-11-29-hosting-my-own-short-urls-with-yourls-and-docker/</guid><pubDate>Wed, 29 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;전 예전부터 단축 URL 서비스를 호스팅하고 싶었습니다. 주 목적으로는 가끔씩 링크를 나눠줘야 되는 경우가 있는데, 링크는 만료되기 일쑤거든요. (엔트로피를 파괴하거나 우주의 붕괴를 막지 않는다면 말이죠!) 단축 URL 서비스가 있다면 (거의) 영구적인 링크를 나눠줄 수 있는데, 지구가 멸망하거나 서버 요금을 내는 것을 깜빡해서 서버가 종료되기 전까지는 링크를 계속 업데이트할 수 있기 때문이죠.&lt;/p&gt;
&lt;p&gt;그래서 YOURLS라는 프로젝트와 Docker(+ Docker Compose)를 사용하여 한번 설정해봤습니다. 어려울 줄 알았는데, 이 파일 하나면 거의 설치가 모두 끝납니다:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;3&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  mariadb&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;mariadb:11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    container_name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls-mariadb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    restart&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;unless-stopped&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    environment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ROOT_PASSWORD_HERE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      MYSQL_PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;DATABASE_PASSWORD_HERE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      MYSQL_DATABASE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      MYSQL_USER&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;./mariadb:/var/lib/mysql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  yourls&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls:latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    container_name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls-app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    restart&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;unless-stopped&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    depends_on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;mariadb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    environment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_DB_HOST&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;mariadb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_DB_USER&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_DB_PASS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;DATABASE_PASSWORD_HERE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_DB_NAME&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_USER&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ADMIN_USERNAME_HERE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_PASS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ADMIN_PASSWORD_HERE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_SITE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://yourls.example.com&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_UNIQUE_URLS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_PRIVATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;./yourls:/var/www/html/user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  swag&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;linuxserver/swag:latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    container_name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;swag&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    restart&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;unless-stopped&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    environment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;PUID=1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;PGID=1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;TZ=Etc/UTC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;URL=yourls.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;VALIDATION=http&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;EMAIL=letsencrypt-email@example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;./swag/config:/config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;80:80&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;443:443&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    depends_on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;docker-compose up -d&lt;/code&gt;를 사용하여 실행한 다음, &lt;code&gt;swag/&lt;/code&gt; 폴더 안 샘플 설정 파일을 복사한 후 설정값 몇개만 건드려주면 설치가 끝납니다.&lt;/p&gt;
&lt;p&gt;예시로 이 블로그 글로 연결되는 단축 링크는 다음과 같습니다: &lt;a href=&quot;https://links.ericswpark.com/blog-ko-yourls&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://links.ericswpark.com/blog-ko-yourls&lt;/a&gt; (너무 재귀하죠!)&lt;/p&gt;</content:encoded></item><item><title>Whisper와 함께 자동으로 제 둥영상 자막을 만들어봤습니다</title><link>https://ericswpark.com/ko/blog/2023/2023-08-08-automatically-generate-captions-for-my-videos-with-whisper/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2023/2023-08-08-automatically-generate-captions-for-my-videos-with-whisper/</guid><pubDate>Tue, 08 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;둥영상의 자막을 작성하는 일은 하도 시간이 많이 걸리기에, 하기 싫어합니다. 그래서 &lt;a href=&quot;https://github.com/openai/whisper&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;OpenAI의 Whisper 프로그램 (및 모델)&lt;/a&gt;과 &lt;a href=&quot;https://github.com/ggerganov/whisper.cpp/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Georgi Garanov가   C++로 다시 작성한 프로그램&lt;/a&gt;을 찾았을 때, 몇 시간씩 수작업을 하지 않으려고 확인해봤습니다.&lt;/p&gt;
&lt;p&gt;FCPX에서 MP3 파일을 만든 후, &lt;code&gt;ffmpeg&lt;/code&gt;를 사용하여 Whisper.cpp가 원하는 WAV 파일로 먼저 변환해줬습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ffmpeg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input.mp3&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -acodec&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pcm_s16le&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -ac&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -ar&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 16000&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그 다음으로, Whisper.cpp를 설치했죠:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; clone&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://github.com/ggerganov/whisper.cpp.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; whisper.cpp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# large 모델까지는 필요가 없겠지만, 전 사용했습니다&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;bash&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./models/download-ggml-model.sh&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; large&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;make&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; large&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 컴퓨터의 CPU에 알맞게 쓰레드 수를 조정합니다&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;./main&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -t&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --output-srt&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --language&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; en&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --model&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./models/ggml-large.bin&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --file&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/Downloads/output.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;제공한 오디오 파일은 약 12분 정도의 길이였는데, Whisper.cpp가 분석하는데 사용한 시간은 약 5분 정도였습니다.&lt;/p&gt;
&lt;p&gt;출력한 SRT 파일을 FCPX로 가져와서 한번 확인해봤습니다. 몇몇 인식 오류가 있긴 있었지만, 제 끔찍한 목소리를 거의 90%나 정확하게 판독해낸 것 같았습니다. 찾은 (비교적 작은) 단점 하나는 각 줄이 스페이스로 시작한다는 점이었는데, 아마도 Whisper.cpp가 Whisper가 만들어낸 VTT 파일을 SRT로 변환하면서 생긴 오류가 아닌가 싶습니다. 그것 외에는 완벽했습니다. 제 목소리를 더 이상 수작업으로 적어내릴 필요가 없죠.&lt;/p&gt;</content:encoded></item><item><title>부드러운 둥영상 탐색, 버전 2</title><link>https://ericswpark.com/ko/blog/2023/2023-08-07-smooth-scrubbing-videos-version-2/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2023/2023-08-07-smooth-scrubbing-videos-version-2/</guid><pubDate>Sun, 06 Aug 2023 23:48:32 GMT</pubDate><content:encoded>&lt;p&gt;이 글은 &lt;a href=&quot;/ko/blog/2022/2022-11-07-smooth-scrubbing-videos&quot;&gt;원글&lt;/a&gt;에 대한 업데이트입니다. 부드러운 탐색이 되는 둥영상을 재인코딩하는데 사용할 수 있는 새로운 정보와 방식을 찾았기 때문에 작성합니다.&lt;/p&gt;
&lt;h1 id=&quot;prores&quot;&gt;ProRes?&lt;/h1&gt;
&lt;p&gt;애플의 ProRes 코덱은 둥영상 프레임을 압축하지 않은 상태로 저장하는데, 컴퓨터들은 이를 디코딩하는데 많은 시간을 들일 필요가 없습니다. (H264처럼 압축된 코덱에 비하면 말이죠.)&lt;/p&gt;
&lt;p&gt;그럼 빨리 사용하실 수 있는 예시 명령문을 적어보겠습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 \   # 입력 파일&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -c:v prores_ks    \   # 사용할 ProRes 인코더 (prores와 prores_aw도 존재합니다, 다만)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -profile:v 3      \   # 사용할 ProRes 프로파일 (밑을 참고하세요)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -qscale:v 11      \   # 출력물의 &quot;품질&quot; (9-11 사이가 좋습니다; 낮을수록 좋습니다)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -vendor apl0      \   # 애플 프로그램을 속이는 값&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  output.mov            # ProRes를 지원하는 컨테이너여야 합니다 (나머지 두 개는 mkv와 mxf)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;인코더&quot;&gt;인코더&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;prores&lt;/code&gt;와 &lt;code&gt;prores_aw&lt;/code&gt;는 사용하지 마세요. 위에 적어두긴 했지만, 속도도 느리고 결과물도 그렇게 좋지 않습니다. 그냥 &lt;code&gt;prores_ks&lt;/code&gt;를 사용하세요.&lt;/p&gt;
&lt;h2 id=&quot;프로파일&quot;&gt;프로파일&lt;/h2&gt;
&lt;p&gt;ffmpeg ProRes 프로파일들은 다음 정수값으로 매핑되어 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;proxy - &lt;code&gt;0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;lt - &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;standard - &lt;code&gt;2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;hq - &lt;code&gt;3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;4444 - &lt;code&gt;4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;4444xq - &lt;code&gt;5&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;3&lt;/code&gt;을 사용하시면 되는데, 만약 4:2:2 크로마 서브샘플링 (chroma subsampling) 대신 4:4:4를 쓰셔야 될 경우 상위 옵션을 사용하시면 됩니다. 그렇다면 &lt;code&gt;-pix_fmpt yuva444p101e&lt;/code&gt; 명령문도 추가하셔야 됩니다. (이게 무슨 뜻인지 모르시겠다면 그냥 &lt;code&gt;3&lt;/code&gt; 이하로 사용하세요.)&lt;/p&gt;
&lt;h2 id=&quot;qscale&quot;&gt;qscale&lt;/h2&gt;
&lt;p&gt;밑에 링크해둔 설명서에 잘 나열되어 있는데, 빠르게 읽으실 수 있도록 한번 더 설명해보겠습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0이 최고 품질, 32가 최악&lt;/li&gt;
&lt;li&gt;권장값: 9-13&lt;/li&gt;
&lt;li&gt;지나친 설정값 (공간 제약이 없을 때): 5 이하&lt;/li&gt;
&lt;li&gt;경고: 0에 가까워지면 둥영상 파일이 너무 커져 읽을 수 있는 기기가 없기에 재생이 불가능해집니다!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 명령문과 설명은 &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/VFX#Prores&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;공식 ffmpeg 설명서&lt;/a&gt;에서 가져왔는데, 더 많은 정보가 필요하시다면 링크를 참조해주세요.&lt;/p&gt;
&lt;p&gt;그리고 아쉽게도, 밑에 적혀있는 H264 방법처럼 하드웨어 가속이 적용된 인코더를 ProRes에서 사용할 수 없었습니다. 매번 재인코딩할 때마다 계속 이상하고 이해가 되지 않는 오류를 내놓는데, 이걸 어떻게 고치는지 아신다면 이 부분을 수정할 수 있도록 댓글을 남겨주세요!&lt;/p&gt;
&lt;h1 id=&quot;h264&quot;&gt;H264&lt;/h1&gt;
&lt;p&gt;부드러운 탐색이 가능한 H264 파일을 만드는데 잠시 수정할 부분이 있어서 짚고 넘어가겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/VFX#Frame-by-Framescrubbing&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;ffmpeg의 공식 설명서&lt;/a&gt;를 바탕으로, &lt;strong&gt;가장 부드러운&lt;/strong&gt; 탐색이 가능한 둥영상을 만드려면, B프레임에 추가로 P프레임도 없애셔야 합니다. 그리고 이 명령이 그걸 가능하게 하죠:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 \   # 입력 파일&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -c:v libx264      \   # H264 인코더&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -profile:v main   \   # H264 프로파일&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -g 1              \   # P프레임 없애기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -crf 9            \   # 출력물의 &quot;품질&quot; (7-11 사이가 좋습니다; 낮을수록 좋습니다)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -bf 0             \   # B프레임 없애기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -vendor apl0      \   # 애플 프로그램을 속이는 값&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 마지막 팁을 드리자면, 만약 애플 실리콘 맥을 사용하고 계실 경우, 하드웨어 가속을 사용하여 둥영상 인코딩을 빠르게 하실 수 있습니다! 그냥 &lt;code&gt;-c:v libx264&lt;/code&gt;를 &lt;code&gt;-c:v h264_videotoolbox&lt;/code&gt;로 대체하고, &lt;code&gt;-crf 9&lt;/code&gt; 명령문을 &lt;code&gt;-b:v 8000k&lt;/code&gt;로 바꿉니다 (이 인코더는 crf는 지원하지 않고 고정 비트레이트 설정만 지원하기 때문입니다). 필요에 따라 값을 수정하는데, 원본 파일을 참고하세요.&lt;/p&gt;
&lt;p&gt;그리고 마지막-마지막 팁: &lt;a href=&quot;https://github.com/althonos/ffpb&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;ffpb를 한번 확인해보세요&lt;/a&gt;! ffmpeg 작업에 진행 상태바를 추가합니다.&lt;/p&gt;</content:encoded></item><item><title>GL.iNet 라우터 안 고장난 SD 카드 관련 팁</title><link>https://ericswpark.com/ko/blog/2023/2023-08-03-tip-on-faulty-sd-cards-in-gl-inet-routers/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2023/2023-08-03-tip-on-faulty-sd-cards-in-gl-inet-routers/</guid><pubDate>Wed, 02 Aug 2023 15:33:38 GMT</pubDate><content:encoded>&lt;p&gt;여행용 라우터가 SD 카드를 인식하도록 약 한 시간 정도를 허비한 후 이 글을 작성하여 다른 분이 짜증나고 화나는 일을 겪지 않도록 도와드릴려고 합니다.&lt;/p&gt;
&lt;p&gt;오늘 문제의 대상은 GL.iNet 라우터 빼고 다른 기기에서 잘 작동하는 SD 카드입니다. 그럼 먼저 의심할 부분은 SD 카드의 포맷이겠죠? 그래서 다 시도해봤습니다. 파티션 태이블은 GPT와 MBR, 그리고 파티션의 실제 포맷은 &lt;code&gt;ext4&lt;/code&gt;, exFAT, NTFS, 그리고 FAT32까지 전부 다 시도해봤습니다. 하지만 아무리 조합을 해봐도 인식이 되질 않는 겁니다.&lt;/p&gt;
&lt;p&gt;그래서 라우터에 SSH로 접속해 봤더니, &lt;code&gt;dmesg&lt;/code&gt;에서 다음과 같은 줄이 눈에 띕니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[  280.757666] mmc0: mmc_rescan_try_freq: reset power&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SD 카드를 빼고 나서 다시 삽입하면 계속 이 줄이 반복됐습니다.&lt;/p&gt;
&lt;p&gt;혹시나 해서, 정상 작동하는 것을 확인한 킹스턴 (Kingston) 브랜드 SD 카드를 사용해봤습니다. 그랬더니 라우터가 곧바로 카드를 인식하는 겁니다. &lt;code&gt;dmesg&lt;/code&gt; 상에는:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[  444.254378] mmc0: new ultra high speed SDR104 SDHC card at address 0007&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[  444.264841] mmcblk0: mmc0:0007 SD16G 14.4 GiB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[  444.265892]  mmcblk0: p1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 카드가 추출되었을때:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[  448.910935] mmc0: card 0007 removed&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그래서 작동하지 않은 SD 카드를 살펴보니, 무슨 이름도 없는 SD 카드였던 겁니다. 라우터가 인식을 못할 정도로만 고장났음을 직감하고, 카드를 반으로 쪼게어 버렸습니다.&lt;/p&gt;
&lt;p&gt;꿀팁: 만약 기기가 SD 카드를 인식하지 않는다면, 다른 걸 시도해보세요.&lt;/p&gt;
&lt;p&gt;추가적으로, 포맷은 그렇게 중요하지 않았습니다. (적어도 제가 가지고 있는 GL-AXT1800) 라우터는 exFAT, FAT32, NTFS, 그리고 &lt;code&gt;ext4&lt;/code&gt;까지 전부 지원했습니다. GPT 파티션 태이블을 지원하는지는 모르겠지만, 기반을 OpenWRT로 하다 보니 지원을 안하는 게 이상할 듯 합니다.&lt;/p&gt;</content:encoded></item><item><title>엑스웨이는 USB-C 스펙을 제대로 따르지 않습니다</title><link>https://ericswpark.com/ko/blog/2023/2023-07-26-exway-doesnt-care-about-usb-c-conformity/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2023/2023-07-26-exway-doesnt-care-about-usb-c-conformity/</guid><pubDate>Wed, 26 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;5월 정도에 엑스웨이(Exway)에서 전동스케이트보드를 구입한 후, 오늘 처음으로 리모컨을 충전했습니다. 그런데, USB-C 충전기에 꼽았는데 불이 안 들어오는 겁니다.&lt;/p&gt;
&lt;p&gt;타입C 케이블의 반대쪽이 충전기의 타입C 포트에 연결되어 있다는 것을 깨달았을 때부터 살짝 불안하기 시작했습니다. 만약 우려한 것이 사실이라면, 시중에 제대로 설계되지 않은 기기를 판매하는 꼴이기 때문이죠. 우려한 대로, USB-A-to-C 케이블로 리모컨을 연결하자 바로 켜지고 충전이 시작되었습니다.&lt;/p&gt;
&lt;p&gt;그럼 이 글에서는 Exway에 보낸 제 지원 티켓 이메일, Exway의 응답, 그리고 왜 USB-C 스펙을 위반하는 장치를 &lt;strong&gt;구매하면 안 되는지&lt;/strong&gt;에 대해 살펴보겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;exway에-보낸-지원-티켓&quot;&gt;Exway에 보낸 지원 티켓&lt;/h1&gt;
&lt;p&gt;초기로 보낸 이메일입니다 (영문 원본으로 게재하겠습니다):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt;  &lt;br&gt;
Title: Exway remote missing USB-C resistor &lt;br&gt;
To: &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 13:24:11 +0900&lt;/p&gt;
&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;I noticed that the remote for the Exway Flex has a missing resistor on the USB-C port. This makes it so that it will not charge with a type-C to C cable. Is this problem fixed with newer revisions of the remote?&lt;/p&gt;
&lt;p&gt;Thanks, &lt;br&gt;
Eric&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;답장:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Title: [Exway Board] Re: Exway remote missing USB-C resistor &lt;br&gt;
To: Exway &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 08:12:09 +0000&lt;/p&gt;
&lt;p&gt;Hi Eric,&lt;/p&gt;
&lt;p&gt;Thanks for reaching&lt;/p&gt;
&lt;p&gt;The new remote is using the USB-C charge port&lt;/p&gt;
&lt;p&gt;Best &lt;br&gt;
Exway after-sale support team&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;이메일은 제대로 읽어보지도 않는군요…&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Title: Re: [Exway Board] Exway remote missing USB-C resistor &lt;br&gt;
To: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 17:42:40 +0900&lt;/p&gt;
&lt;p&gt;Hi Exway,&lt;/p&gt;
&lt;p&gt;I think you misunderstood my question. My remote does indeed have a USB-C port. However, it is missing the proper resistor that tells the connected type-C cable that it draws power. As a result the remote does not charge with a type-C-to-C cable.&lt;/p&gt;
&lt;p&gt;My question was, whether or not this is fixed in newer revisions of the remote.&lt;/p&gt;
&lt;p&gt;Thanks, &lt;br&gt;
Eric&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;답장:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Title: [Exway Board] Re: Exway remote missing USB-C resistor &lt;br&gt;
To: Exway &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 08:59:22 +0000&lt;/p&gt;
&lt;p&gt;Hi Eric,&lt;/p&gt;
&lt;p&gt;Thanks for getting back&lt;/p&gt;
&lt;p&gt;Then we suggest you change the cable to USB- C port, C-C cable is still not able to be compatible&lt;/p&gt;
&lt;p&gt;Best &lt;br&gt;
Exway after-sale support team&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;중국 회사에서 예상하고 있던 답변이었지만, 다시 한번 이메일을 보내봤습니다:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Title: Re: [Exway Board] Exway remote missing USB-C resistor &lt;br&gt;
To: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 18:00:54 +0900&lt;/p&gt;
&lt;p&gt;Hi Exway,&lt;/p&gt;
&lt;p&gt;Thank you for the confirmation. Is a fix planned in future revisions? Because the device violates USB-C specifications by not charging with C-to-C cables.&lt;/p&gt;
&lt;p&gt;Thanks, &lt;br&gt;
Eric&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;다시 제 이메일을 잘못 이해했네요:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Title: [Exway Board] Re: Exway remote missing USB-C resistor &lt;br&gt;
To: Exway &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 09:04:09 +0000&lt;/p&gt;
&lt;p&gt;Hi Eric,&lt;/p&gt;
&lt;p&gt;Thanks for getting back&lt;/p&gt;
&lt;p&gt;The remote just doesn’t have the agreement, not able to change it, thanks for your support&lt;/p&gt;
&lt;p&gt;Best &lt;br&gt;
Leo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;그래서 하드웨어 부분에 대해서 물어봤습니다:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Title: Re: [Exway Board] Exway remote missing USB-C resistor &lt;br&gt;
To: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 18:06:59 +0900&lt;/p&gt;
&lt;p&gt;Hi Leo,&lt;/p&gt;
&lt;p&gt;Yes, I understand that a firmware update will not resolve this issue, as it is a hardware problem.&lt;/p&gt;
&lt;p&gt;However, for future revisions of this remote, this problem should be fixed, as USB-C spec conformity is very important for all devices shipping with a type-C port. I hope that this is forwarded over to the engineers so that they can incorporate it in future remote revisions. In fact, devices that do not meet this spec can be determined as defective in some jurisdictions.&lt;/p&gt;
&lt;p&gt;Thanks, &lt;br&gt;
Eric&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;마지막 답변입니다:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Title: [Exway Board] Re: Exway remote missing USB-C resistor &lt;br&gt;
To: Exway &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 09:22:05 +0000&lt;/p&gt;
&lt;p&gt;Hi Eric,&lt;/p&gt;
&lt;p&gt;I have reported your request and I referred this to the tech team, but I  got a negative answer, sorry about that&lt;/p&gt;
&lt;p&gt;Best &lt;br&gt;
Leo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;따라서 현재로썬 Exway의 입장은, 문제를 알고 있지만 해결하진 않을 것이며 향후 수정할 계획이 없다는 것입니다.&lt;/p&gt;
&lt;h1 id=&quot;문제가-뭐였죠&quot;&gt;문제가 뭐였죠?&lt;/h1&gt;
&lt;p&gt;USB-C 포트는 다른 포트와 다릅니다 [출처 필요]. 특히, 기본적으로 5볼트의 전력을 공급하는 USB-A 포트와 달리, USB-C는 전력 공급을 시작하기 전에 협상 절차가 필요합니다. USB-C 충전기의 브랜딩 자료에서 들어보셨을 수도 있는데, USB-PD 또는 USB Power Delivery가 장치가 준수해야 하는 스펙입니다.&lt;/p&gt;
&lt;p&gt;하지만 협상 절차를 담당하는 PD 칩들은 기기의 가격에 비교하면 꽤 비쌉니다. 위에서 얘기했던 전동스케이트보드의 리모컨은 아마도 5천원 정도밖에 되지 않을 겁니다. (실제로는 대량 생산을 해서 훨씬 저렴하겠죠.) 칩이 5백원이더라도, 기기의 부품값 중에서 상당한 비중을 차지하게 됩니다.&lt;/p&gt;
&lt;p&gt;하지만 당연하게도, USB-IF 단체는 이에 대비하여 기기들이 5V의 전력만을 원할때 협상 절차 없이 기기가 어떻게 작동해야 하는지 (자체 식별해야 하는지) 설명을 해두었습니다.&lt;/p&gt;
&lt;p&gt;CC 핀들을 5.1K값 저항으로 접지(ground)하기만 하면 이 식별 절차가 완성됩니다. (CC 핀이 2개가 있는데 (CC1과 CC2), 따라서 저항도 2개가 필요합니다.) 이렇게 설계를 안 할 경우, 타입C-to-C 케이블과 충전기로는 해당 기기를 충전할 수 없게 됩니다. 충전기는 타입C 케이블을 따라 CC 핀들을 확인하는데, 만약 (저항으로) 접지가 안되어 있을 경우 필요한 5V를 보내지 않기 때문입니다. (그리고 만약 기기가 저항을 하나만 사용하거나, &lt;a href=&quot;https://medium.com/@leung.benson/how-to-design-a-proper-usb-c-power-sink-hint-not-the-way-raspberry-pi-4-did-it-f470d7a5910&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;CC 핀 두개에 한 저항을 한꺼번에 연결한다면&lt;/a&gt; (라즈베리파이 제단의 잘못이죠!!!), 여러 가지 이상한 문제가 발생할 수 있습니다. 예를 들어, 타입C를 꼽았을 때 한쪽으로만 충전된다면 이 문제일 가능성이 높습니다.)&lt;/p&gt;
&lt;p&gt;이 기기의 경우에는, Exway가 리모컨의 CC 핀에 필요한 두 개의 저항을 넣지 않았고, 따라서 USB-C-to-C 케이블로는 충전이 되질 않습니다.&lt;/p&gt;
&lt;h1 id=&quot;그러면-뭐가-문제죠&quot;&gt;그러면 뭐가 문제죠?&lt;/h1&gt;
&lt;p&gt;거의 대부분은 이렇게 반응하실 겁니다. “그냥 USB-A-to-C 케이블로 충전하면 되는 거 아닌가요?”&lt;/p&gt;
&lt;p&gt;물론, 이 방법으로 리모컨을 충전할 수는 있습니다. 하지만 큰 그림을 보면 문제가 더 있습니다.&lt;/p&gt;
&lt;p&gt;이 저항들은 넣는데 가격이 &lt;del&gt;몇십원&lt;/del&gt; &lt;a href=&quot;https://github.com/ericswpark/ericswpark.github.io-comments/discussions/7#discussioncomment-7836362&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;몇원&lt;/a&gt; (GitHub상 @rickcox 수정해주셔서 감사합니다!) 밖에 되지 않습니다. 그리고 포함하는 설명은 구글 검색 한번으로 찾아볼 수 있죠. 사실, 제대로 배운 전자공학 엔지니어로 일하면서 회로를 디자인한다면, 가장 먼저 해야 될 일이 기기에 포함시키기 전에 사양표를 읽는 것임은 누구든지 알고 있겠죠.&lt;/p&gt;
&lt;p&gt;그럼 다음 중 한 가지 경우입니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Exway가 일을 잘 못하는 엔지니어들을 고용했거나&lt;/li&gt;
&lt;li&gt;엔지니어들이 포트가 스펙에 맞지 않다고 경고했지만, 돈을 아끼기 위해 Exway가 무시했거나,&lt;/li&gt;
&lt;li&gt;Exway의 엔지니어링 팀이 실수로 저항을 넣는 것을 깜빡했거나, 이 스펙에 대해선 모르고 있었을 경우가 있습니다. (아니, USB-C 스펙표는 엄청 길고 복잡해서 그럴 만도 합니다. USB-IF 협회가 하도 바꾸는게 많아서…)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;하지만 세번째 경우는 가능성이 낮다고 생각합니다. 개발 도중 한번도 기기를 시험해보지 않았다고요? 아무도 엔지니어링 샘플들을 사용하면서 타입C-to-C 케이블로 리모컨을 충전해보지 않았다고요? Exway가 이 문제를 몰랐을 가능성은 거의 불가능에 가까운데, &lt;em&gt;물론&lt;/em&gt; 기기를 아예 시험해보지 않았을 수도 있지만 이것 역시 믿기 어렵습니다. (그리고 실제로 기기들을 시험해보지 않는다면 더 끔찍하고요.)&lt;/p&gt;
&lt;p&gt;그리고 이제는 문제를 알고 있음을 확인했지만, 상관하지 않는다는 것도 보여줬습니다. 문제를 알려줬음에도 전혀 고칠 생각을 하지 않죠.&lt;/p&gt;
&lt;p&gt;만약 _이런 곳_에서 원가 절감을 한다면, 이 회사가 만든 생명에 위험할 수도 있는 전동스케이트보드하고 자체 생산한 배터리를 정말 사용하고 싶으세요? 다른 안전 부문에서 모든 걸 제대로 했다고 가정하더라도, 만약 배터리가 갑자기 터지는 문제 등이 발생할 경우 위에 답장같이 대응한다면 제대로 안 고쳐줄 것 같습니다.&lt;/p&gt;
&lt;h1 id=&quot;그럼-어떻게-하죠&quot;&gt;그럼 어떻게 하죠?&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;만약 이 문제가 있는 Exway 보드를 가지고 계신다면&lt;/strong&gt;, Exway에 지원 티켓을 열어서 고쳐달라고 합니다. 이건 설계 결함이고, 당연히 고쳐줘야 하는 문제입니다.&lt;/p&gt;
&lt;p&gt;추가로, Exway의 스케이트보드를 사용하지 않는 것을 고려해보세요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;만약 전동 스케이트보드의 구입을 고려하고 계셨다면&lt;/strong&gt;, 다른 브랜드들을 알아보세요.&lt;/p&gt;
&lt;p&gt;가장 중요한 건, 이 글을 공유하여 제조업체들이 &lt;strong&gt;2023년에도 USB-C 스펙을 제대로 안 따르는 기기들을 출시하는 건 용납할 수 없다&lt;/strong&gt;는 것을 알리는 겁니다. 이 문제에 대해서 이미 수많은 포럼 글들이 올라와 있고, r/USBCHardware라는 서브레딧에서도 이런 불량 기기들에 대한 글들이 수시로 올라옵니다. 특정 케이블로 기기가 충전되지 않는 이유를 이해하지 못하는 사람들을 위해, 이러한 회사들이 잘못된 설계로 만들어진 제품들을 판매하지 못하도록 도와주세요.&lt;/p&gt;
&lt;h1 id=&quot;링크들&quot;&gt;링크들&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://www.reddit.com/r/Exway/comments/15a5myg/exway_doesnt_care_about_usbc_conformity/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;r/Exway상 토론 (영문)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.reddit.com/r/UsbCHardware/comments/15a5o3f/oc_exway_doesnt_care_about_usbc_conformity/?&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;r/USBCHardware상 토론 (영문)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=36886859&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Hacker News상 토론 (영문)&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>인텔 맥에서 애플 실리콘 맥으로 홈브루 이동하기</title><link>https://ericswpark.com/ko/blog/2023/2023-06-17-migrating-homebrew-from-intel-to-asi-mac/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2023/2023-06-17-migrating-homebrew-from-intel-to-asi-mac/</guid><pubDate>Sat, 17 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;밑에 나열된 방법으로 인텔 기반 맥에서 애플 실리콘 기반 맥(여기서부터 ASi라고 줄여 적겠습니다)으로 홈브루(Homebrew)를 이동했습니다.&lt;/p&gt;
&lt;h1 id=&quot;준비하기&quot;&gt;준비하기&lt;/h1&gt;
&lt;p&gt;이동하기 전에, 다음 명령을 인텔 맥에서 실행하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew bundle dump&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 명령은 현재 설치된 패키지 목록을 현재 폴더 안 &lt;code&gt;Brewfile&lt;/code&gt; 안에 저장합니다. 이를 다른 곳에 복사해두거나, 맥에서 맥으로 바로 데이터를 전송한다면 그대로 내버려두세요.&lt;/p&gt;
&lt;h2 id=&quot;인텔-맥을-갖고-있지-않으면요&quot;&gt;인텔 맥을 갖고 있지 않으면요?&lt;/h2&gt;
&lt;p&gt;위 명령을 ASi 맥에서 실행하면 실패하는데, 명령을 &lt;code&gt;git&lt;/code&gt;을 요구하고, 인텔 버전의 &lt;code&gt;git&lt;/code&gt;은 ASi에서 크래시하기 때문입니다.&lt;/p&gt;
&lt;p&gt;따라서 밑의 ASi 홈브루 설치를 마친 후, 다음을 실행하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 인텔 홈브루 터미널 창에서&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;brew&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; uninstall&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ASi 홈브루 터미널 창에서&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;brew&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 인텔 홈브루 터미널 창에서&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;brew&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bundle&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dump&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그런 다음, “인텔 버전 홈브루 삭제하기”부터 진행하세요.&lt;/p&gt;
&lt;h1 id=&quot;asi-홈브루-설치하기&quot;&gt;ASi 홈브루 설치하기&lt;/h1&gt;
&lt;p&gt;이 과정은 꽤 간단합니다. 다음 홈브루 설치 명령을 다시 실행하면 되거든요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/bin/bash&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -c&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;$(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -fsSL&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그러면 설치 프로그램이 다음 두 명령을 주는데, 이를 실행해야 홈브루가 실행 변수에 추가됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;eval &quot;$(/opt/homebrew/bin/brew shellenv)&quot;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; /Users/ericswpark/.zprofile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;eval&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;$(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/opt/homebrew/bin/brew&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; shellenv)&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(당연히, 표시되는 명령에서 사용자 이름이 다르겠지만요.)&lt;/p&gt;
&lt;h1 id=&quot;인텔-버전-홈브루-삭제하기&quot;&gt;인텔 버전 홈브루 삭제하기&lt;/h1&gt;
&lt;p&gt;다음 명령들을 실행하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/Downloads&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# (인텔 버전) wget이 ASi상 실행되지 않기에&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; uninstall.sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/bin/bash&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./uninstall.sh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /usr/local&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;삭제 프로그램이 모든 것을 삭제하기에 권한이 충분치 않았기 때문에, 실행이 완료된 후 제가 직접 &lt;code&gt;/usr/local&lt;/code&gt;로 들어가서 안의 모든 파일과 폴더들을 삭제해야 했습니다. macOS가 기본적으로 그 폴더를 탑재하지 않고 출고되기에, 그 폴더 안 모든 것을 삭제해도 &lt;em&gt;대부분&lt;/em&gt; 무방하지만, 만약 폴더 안의 것들에 의존하는 것들이 있다면, 조심해서 삭제하시는 것을 추천합니다. 저는 그냥 부러지는대로 다시 설치할 예정이라, 폴더 안 모든 것을 삭제했습니다.&lt;/p&gt;
&lt;p&gt;참고: &lt;a href=&quot;https://github.com/porg&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;GitHub상 @porg께서&lt;/a&gt; 다른 도구들과 패키지 매니저들도 &lt;code&gt;/usr/local&lt;/code&gt;에 설치되어 있을 수 있기 때문에, 혹시 모르니 삭제 전 확인하는 것을 권장합니다.&lt;/p&gt;
&lt;p&gt;또 인텔 버전 홈브루가 시스템 변수 상에 있게 해준, 직접 추가한 변경사항들을 닷파일(dotfile, 유닉스 기반 시스템에서 설정값을 저장하는 숨겨진 파일들)에서 삭제해야 할 수도 있습니다. 제 경우에는, &lt;code&gt;/usr/local&lt;/code&gt;에 위치한 이전 홈브루 설치를 가리키는 줄들을 &lt;code&gt;.zshrc&lt;/code&gt;에서 삭제해야 했습니다.&lt;/p&gt;
&lt;h1 id=&quot;모든-것을-재설치하기&quot;&gt;모든 것을 재설치하기&lt;/h1&gt;
&lt;p&gt;이제 새로운 터미널 창을 열어 ASi 버전의 홈브루를 활성화시키세요.&lt;/p&gt;
&lt;p&gt;방금 만들었던 &lt;code&gt;Brewfile&lt;/code&gt;이 담겨 있는 폴더로 이동하여 다음을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;brew&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bundle&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --file&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./Brewfile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 이동이 완성됩니다!&lt;/p&gt;</content:encoded></item><item><title>AI 탐지기의 심각한 문제점</title><link>https://ericswpark.com/ko/blog/2023/2023-05-06-these-ai-detectors-are-getting-out-of-hand/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2023/2023-05-06-these-ai-detectors-are-getting-out-of-hand/</guid><pubDate>Sat, 06 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;최근 들어, 학생들이 학교에서 “AI” 도구를 이용한 부정행위로 고발된 사례들이 Reddit에 많이 게시되는 점을 눈치챘습니다. 처음 본 글은 바로 이것이었는데:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://reddit.com/r/college/comments/136tc9i/i_have_been_falsely_accused_of_using_ai_on_my/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;AI를 사용하여 에세이를 썼다고 누명을 썼습니다&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그 글을 시작으로, 이런 사례들이 더욱더 공유되기 시작했습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://reddit.com/r/college/comments/138ug6h/i_was_falsely_accused_of_using_a_ai_to_write_my/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;최종 논문을 AI로 작성했다고 누명을 썼습니다&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://reddit.com/r/college/comments/138xre6/after_seeing_many_false_ai_usage_accusations_here/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;AI 관련 글들을 본 이후로 저에게도 누명이 씌워졌습니다.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그럼 여기에서 가능한 상황을 나열해보겠습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;위의 모든 학생들은 거짓말하고 있고, 정말로 AI를 사용했다&lt;/li&gt;
&lt;li&gt;거짓말이 아니고, AI 탐지 소프트웨어가 실수했다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;간단하네요. 그럼 AI 탐지 소프트웨어가 어떻게 작동하는지 알아보겠습니다. 그걸 할려면, 일단 예시가 필요한데, Turnitin의 AI 탐지기를 한번 살펴보겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;왜-turnitin이죠&quot;&gt;왜 Turnitin이죠?&lt;/h1&gt;
&lt;p&gt;일단 편견부터 공개하겠습니다: 전 Turnitin을 지극히 싫어하거든요.&lt;/p&gt;
&lt;p&gt;제 고등학교 시절 당시, Turnitin은 제가 쓴 글이 표절이라고 주장하였고, 같은 반 친구 한 명은 실수로 표절하였는지 여부를 확인하기 위해 별도의 시험 Turnitin 환경에서 제출한 글 때문에, 실제 학교 Turnitin에 제출된 글에 100% 표절 점수를 받았습니다.&lt;/p&gt;
&lt;p&gt;하지만 그건 AI 도구들이 공개되기 이전이였죠. Turnitin의 AI-이전 도구는 “표절”을 “확인”하기 위해 플랫폼에 제출된 모든 텍스트와 글을 비교했습니다. 만약 두 글이 일치하고 일치율이 특정 임계값을 초과하면 그 글은 검토 대상으로 지정되었죠.&lt;/p&gt;
&lt;p&gt;당연하게도, GPT로 치팅을 진행하면 이 방법은 작동하지 않겠죠. 처음에 사용된 프롬프트, 사용된 모델과 플랫폼마다 결과가 크게 달라질 수 있는데, 문제는 실행 간에도 결과가 매우 달라질 수 있죠. 따라서 Turnitin은 하루아침에 쓸모없어지는 것을 피하기 위해 이미 결함투성이인 구식 텍스트 비교 방법을 대체하는, 새로운 솔루션을 개발해야 했습니다.&lt;/p&gt;
&lt;p&gt;그래서 나온게…&lt;/p&gt;
&lt;h1 id=&quot;turnitin의-ai-글-탐지&quot;&gt;Turnitin의 “AI 글 탐지”&lt;/h1&gt;
&lt;p&gt;Turnitin은 “AI-기반 치팅“ 논란을 써먹기 위해 새로운 “AI 탐지“ 솔루션을 개발했는데, 심지어는 &lt;a href=&quot;https://www.turnitin.com/solutions/ai-writing&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;아예 페이지 하나를 만들어&lt;/a&gt; 이 솔루션을 광고하기 시작했습니다.&lt;/p&gt;
&lt;p&gt;만약 실수로라도 그 페이지를 방문한다면, 마케팅 문구, 기업 홍보 어쩌구저쩌구 (“AI 혁신 연구소“)만 가득할 뿐, 실질적으로 유용한 정보는 찾아볼 수 없을 겁니다. Turnitin의 탐지가 어떻게 작동하는지는 아예 설명조차 없죠.&lt;/p&gt;
&lt;p&gt;그 정보는 사이트의 &lt;a href=&quot;https://www.turnitin.com/products/features/ai-writing-detection&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;자주 하는 질문 페이지&lt;/a&gt;에 숨겨져 있는데, 거기에 나와있는 정보마저도 모호하며 도움이 되질 않습니다. 아니, 직접 한번 읽어보세요:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;어떻게 작동하나요?
(…기술적인 쓰레기…) 세그먼트들은 AI 감지 모델에 대입되며, 각 문장에 0과 1 사이의 점수를 주어 인간이 작성했는지 AI가 작성했는지 판단합니다. (…더 많은 기술적 내용…) 현재 Turnitin의 AI 감지 모델은 ChatGPT를 포함한 GPT-3 및 GPT-3.5 언어 모델에서 만들어진 콘텐츠를 감지하도록 훈련되어 있습니다. GPT-4의 글쓰기 특성은 이전 모델 버전과 일치하기 때문에 당사의 감지기는 대부분의 경우 GPT-4(ChatGPT 플러스)의 콘텐츠를 감지할 수 있습니다. 다른 AI 언어 모델이 만든 글을 더 잘 감지할 수 있도록 모델을 확장하기 위해 적극적으로 노력하고 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;Turnitin이 AI가 글을 썼는지 감지하는 방법은 글을 가지고… 또 다른 AI 모델에 집어넣어 인간이 썼는지 AI가 썼는지 예측하는 방식이군요. 마지막 문장을 읽어보면 새로 출시되는 AI 언어 모델마다 새로운 모델을 작성해야 됨을 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;결국 Turnitin은 객관적인 측정값(얼마나 많은 텍스트를 복사하여 붙여넣었는지)에서 AI 블랙박스로 만든, 완전히 주관적인 측정값으로 전환했습니다. 당연히 이 새로운 시스템은 &lt;em&gt;훨씬&lt;/em&gt; 더 신뢰할 수 있죠. (농담입니다.)&lt;/p&gt;
&lt;p&gt;이 예측 점수를 바탕으로 교사들이 학생들의 삶을 완전히 망칠 수 있음에도 불구하고, 이를 신뢰하라고 말하고 있습니다!&lt;/p&gt;
&lt;h1 id=&quot;실은&quot;&gt;실은…&lt;/h1&gt;
&lt;p&gt;Turnitin도 이 탐지 방식에 문제가 있음을 인정했습니다. &lt;a href=&quot;https://www.turnitin.com/blog/understanding-false-positives-within-our-ai-writing-detection-capabilities&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;“AI 글 감지 기능의 오탐지 이해하기”&lt;/a&gt;라는 제목의 블로그 게시물에서 Turnitin은 다음과 같이 주의를 주죠:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;사전에 미리 알기 - 오탐지 가능성을 미리 고려하고 결과를 결정하기 위한 프로세스와 접근 방식에 대한 계획을 세워야 합니다. 더 좋은 방법은 학생들과 이 점을 사전에 얘기하여 기대치를 공유할 수 있도록 하는 것입니다.&lt;/li&gt;
&lt;li&gt;긍정적 의도 가정하기 - 새롭고 알려지지 않은 것이 많은 이 부분에서, 학생들을 최대한 의심하지 마세요. 증거가 불분명할 경우, 학생들이 정직하게 행동할 것이라고 가정합니다.&lt;/li&gt;
&lt;li&gt;개방적이고 정직하게 대응하기 - 오탐지가 있을 수 있음을 미리 인정하는 것이 중요하므로, 교수자와 학생 모두 개방적이고 정직한 대화를 나눌 준비가 되어 있어야 합니다. 오탐지가 발생할 수 있음을 인정하지 않으면 훨씬 더 방어적이고 대립적인 대화가 오고 가면서, 결국 학생과의 관계가 손상될 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;그렇다면 왜 위에서 얘기했던 사례들이 여러 대학을 걸쳐 발생하는 걸까요? 이 면책 조항이 블로그 게시물에 숨겨져 있기 때문이죠. 물론, 저는 Turnitin이 AI 예측 점수 위에 경고 배너를 표시하는지는 모르지만, 표시하더라도 “이 점수는 AI가 다른 AI를 보고 튜링 테스트를 수행하도록 요청하는 것에 불과하기 때문에 완전히 부정확할 수 있습니다”와 비슷한 내용은 절대로 적지 않겠죠. 만약 그랬다면, 교사들의 신뢰를 잃고 회사가 망해버릴 수도 있으니까요.&lt;/p&gt;
&lt;p&gt;따라서, &lt;strong&gt;어떤&lt;/strong&gt; 교사들은 Turnitin의 AI 감지 모델의 예측 점수에 누적되는 오차 범위가 있다는 것을 모르고, 일반적으로 사용될 수 있는 검증된 것으로 착각해버립니다.&lt;/p&gt;
&lt;p&gt;일단 Turnitin 얘기는 여기까지만 하겠습니다. 일반적인 ”AI 감지“에 대해서 잠시 얘기하고 싶거든요.&lt;/p&gt;
&lt;h1 id=&quot;ai-감지-모델들-환각과-데이터세트&quot;&gt;AI 감지, 모델들, “환각”과 데이터세트&lt;/h1&gt;
&lt;p&gt;이러한 “AI 감지“ 도구들이 아무짝에도 쓸모없는 이유는, “AI를 감지”한다는 아이디어 자체에 문제가 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;이렇게 이해하면 쉽습니다: AI 모델들은 때때로 ”환각“하며 완전히 잘못된 정보를 도출할 수 있기 때문에, 출력물을 완전히 신뢰하면 안된다고 알고 있습니다. AI 감지기들은 AI 모델을 기반으로 만들어졌죠. 그럼 왜 저희는 AI 감지기들을 신뢰해야 될까요?&lt;/p&gt;
&lt;p&gt;더 나아가, 이러한 GPT-기반의 도구들은 정보를 제공하기 위해 사용되는 대규모 데이터세트에서 진가를 발휘합니다. 코드를 작성하거나, 요리 레시피를 만들어주거나 등의 작업은 AI 모델들이 하기 쉬운데, 왜냐하면 데이터세트에서 배운 내용을 곧이곧대로 뱉어내면 되기 때문입니다. 하지만 AI 탐지기들은 (이전에 얘기한 GPT 모델들이 뱉어낸) 완전히 새로운 입력을 가지고, 애초에 데이터세트에 포함되지 않았던, ”이 글이 AI가 작성한 글인가”를 판단해야 되기 때문입니다. AI 탐지 이론은 인터넷 상에서 튜링 테스트로밖에 존재하지 않았으며, 이러한 이론들은 지금 바로 시장에 출시되고 있는 GPT 모델들을 꿈에도 상상하지 못했을 겁니다.&lt;/p&gt;
&lt;p&gt;증거로 예시 하나를 들겠습니다. AI 탐지 도구 중 GPTZero라는 서비스는 &lt;a href=&quot;https://www.reddit.com/r/ChatGPT/comments/11ha4qo/gptzero_an_ai_detector_thinks_the_us_constitution/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;실제로 미국의 독립선언서가 AI로 작성된 글이라고 오탐지했죠&lt;/a&gt;. &lt;del&gt;미국 건국의 아버지들이 GPT 모델인줄은 몰랐네요!&lt;/del&gt;&lt;/p&gt;
&lt;h1 id=&quot;그럼-이제-어떻게-되나요&quot;&gt;그럼, 이제 어떻게 되나요?&lt;/h1&gt;
&lt;p&gt;교사들은 이러한 새로운 환경에 맞추어 변해야 될 수 밖에 없습니다. 이는 예전에도 일어났었는데, 인터넷이 처음 등장했을 때 정직하지 않은 학생들은 온라인에서 찾은 자료를 그대로 복붙했죠.&lt;/p&gt;
&lt;p&gt;AI 탐지 예측 점수가 측정 기준이 되면 안되고, 참고 용도로만 사용되야 합니다. 만약 학생이 항상 성적이 나쁜 경우, 이 탐지 점수는 학생이 부정행위를 저지르고 있음을 보여주는 증거의 일부로 사용될 수 있습니다. 반대로, 모범 학생의 에세이 중 하나가 AI로 작성되었다고 플래그된 경우 교사는 당연히 오탐지가 발생했음을 알 수 있겠죠.&lt;/p&gt;
&lt;p&gt;이게 실천되기 위해서는, 모든 AI 탐지 회사들이 탐지 기능들을 훨씬 더 투명성 있게 공개하고, 해당 도구가 부정행위 판단을 돕는 일부일 뿐, 모든 것을 해결할 수 있는 솔루션이 아니라는 점을 강조해야 합니다. 하지만, 이러한 도구는 교육 기관에 판매되어 교사들에게 대량으로 배포되기 때문에, 이를 실행하기 어려울 수 있습니다. 적절한 설명서 및 교육이 없으면 교사들은 도구를 잘못 이해하여 올바르지 않은 방식으로 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;솔직히 마지막 문장을 쓰다 보니 AI-이전의 Turnitin이 떠올랐습니다. 그리고 GPT 챗봇도요. 그리고 현존하는 거의 모든 소프트웨어나 도구도 마찬가지입니다. 사용 중인 도구의 한계와 특성을 완전히 이해하지 못한다면, 아마도 결과값을 맹목적으로 신뢰하면 안되겠죠.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;이 블로그 게시물이 도움이 되었으면 합니다! 만약 본인이나 친구가 로봇으로 오인되었다면 이 블로그 게시물을 교사와 공유하여 AI 탐지기에 의존하는 것이 얼마나 위험한지 알려주세요.&lt;/p&gt;
&lt;h1 id=&quot;수정-2023-07-27&quot;&gt;수정 (2023-07-27)&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=36862850&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;OpenAI가 최근에 AI 탐지기를 없앴는데, 이유는 정확성이 떨어져서라고 합니다&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;가장 유명한 GPT-기반 챗봇 회사가 탐지기를 없앤다면, 다른 회사들은 어떻게 OpenAI보다 더 나은 탐지기들을 만들 수 있을까요? (힌트를 드리자면 만들 수 없고, 만들지도 않습니다.)&lt;/p&gt;</content:encoded></item><item><title>Caddy와 도커를 사용하여 제대로 reverse proxy하는 방법</title><link>https://ericswpark.com/ko/blog/2023/2023-03-18-how-to-reverse-proxy-properly-with-caddy-and-docker/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2023/2023-03-18-how-to-reverse-proxy-properly-with-caddy-and-docker/</guid><pubDate>Sat, 18 Mar 2023 09:52:54 GMT</pubDate><content:encoded>&lt;p&gt;도커 컨테이너 안에서 어떻게 Caddy를 제대로 설정하는지에 관한 튜토리얼입니다.&lt;/p&gt;
&lt;h1 id=&quot;준비사항&quot;&gt;준비사항&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;도커 서비스가 활성화되어 있는 서버. 이 서버 (호스트네임) 이름은 &lt;code&gt;timmy&lt;/code&gt;라고 하겠습니다.&lt;/li&gt;
&lt;li&gt;인터넷에 노출시키고 싶은 서비스들&lt;/li&gt;
&lt;li&gt;이 서비스들을 담고 있는 커스텀 도커 네트워크 (여기에서 &lt;code&gt;proxynetwork&lt;/code&gt;이란 이름을 갖고 있는 것으로 하겠습니다)&lt;/li&gt;
&lt;li&gt;(선택) 인터넷에 노출시키고 싶지 않은 서비스들&lt;/li&gt;
&lt;li&gt;(선택) 위의 서비스들에 접속하는데 사용할 VPN 서비스 (예로 Wireguard)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;왜-reverse-proxy를-사용하죠&quot;&gt;왜 reverse proxy를 사용하죠?&lt;/h1&gt;
&lt;p&gt;왜냐하면 &lt;code&gt;service.timmy.example.com&lt;/code&gt;를 치는 것이 &lt;code&gt;192.168.1.200:4343&lt;/code&gt; 또는 &lt;code&gt;timmy:4343&lt;/code&gt;를 치는 것보다 훨씬 낫기 때문입니다.&lt;/p&gt;
&lt;p&gt;이는 접속할 서비스가 2개나 3개보다 더 많을 때 더욱 그렇습니다. 포트 번호들을 다 기억하는 것은 힘들거든요!&lt;/p&gt;
&lt;h1 id=&quot;1부---외부-레이어&quot;&gt;1부 - 외부 레이어&lt;/h1&gt;
&lt;p&gt;일단, Caddy를 &lt;code&gt;host&lt;/code&gt; 네트워크 옵션을 가지고 설치합니다. (왜 그런지는 나중에 설명할게요.) 그런 다음, 설정에서 필요에 따라 도메인들을 매핑합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# 로깅 설정&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(logs) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    log {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        output file /data/logs/access.log&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# 검색 엔진들에게 서비스들을 숨기기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(no_robots_txt) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    respond /robots.txt 200 {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        body &quot;User-agent: *&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Disallow: /&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        close&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# 메인 페이지&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;timmy.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # 서비스에 reverse proxy를 하거나...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    reverse_proxy 192.168.1.200:8080&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # 다음과 같이 응답할 수 있습니다 (다음 줄을 코멘트 해제하세요)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #respond &quot;Hello World!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    import no_robots_txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    import logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# 다른 서비스들&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;*.timmy.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    import logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    @plex host plex.timmy.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    handle @plex {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        # `172.18.0.1`가 도커 호스트 IP입니다&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy 172.18.0.1:32400&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import no_robots_txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;꽤 일반적인 것들입니다. 컨테이너가 실행되면, Caddy가 Let’s Encrypt를 사용하여 SSL 인증서를 만드는 것과 같은 일들을 수행하는데, 여기에서 &lt;code&gt;timmy.example.com&lt;/code&gt;가 서버의 공개 IP로 설정되어 있다고 하고, &lt;code&gt;*.timmy.example.com&lt;/code&gt;는 &lt;code&gt;timmy.example.com&lt;/code&gt;에 가르키는 CNAME이여야 합니다.&lt;/p&gt;
&lt;p&gt;만약 이게 원했던 것의 전부라면 여기에서 멈추셔도 좋습니다! 하지만 만약 VPN을 통해 접속하고 싶었던 내부 서비스들이 있다면 어떻게 해야 할까요? 그럼 더 진행하겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;2부---내부-서비스들&quot;&gt;2부 - 내부 서비스들&lt;/h1&gt;
&lt;p&gt;이 부분이 꽤 복잡한 부분입니다. 왜냐하면 VPN에 연결된 사람들만이 내부 서비스에 접속할 수 있도록 제한하고 싶기 때문이죠.&lt;/p&gt;
&lt;p&gt;그럼 내부 Caddy 서버부터 먼저 설정하겠습니다. 다음과 같은 &lt;code&gt;Caddyfile&lt;/code&gt;을 만듭니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# 로깅 설정&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(logs) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    log {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        output file /data/logs/access.log&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;:80 {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    @main host timmy.private.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    handle @main {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy foo:8080&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    @jenkins host jenkins.timmy.private.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    handle @jenkins {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy jenkins:8080&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 설정하려면, &lt;code&gt;timmy.private.example.com&lt;/code&gt;는 VPN 네트워크 안의 비공개 IP를 가리켜야 하고, &lt;code&gt;*.timmy.private.example.com&lt;/code&gt;는 &lt;code&gt;timmy.private.example.com&lt;/code&gt;의 CNAME이여야 합니다. 이들을 공개 DNS에 올려도 괜찮은데, 내부 주소라 어차피 외부에서 접속할 수 없기 때문입니다.&lt;/p&gt;
&lt;p&gt;이제 여기에서 들 수 있는 의문이, 왜 서버 블록에 &lt;code&gt;timmy.private.example.com&lt;/code&gt; 또는 &lt;code&gt;*.timmy.private.example.com&lt;/code&gt; 대신 &lt;code&gt;:80&lt;/code&gt;을 사용하는지 궁금할 수도 있습니다. 이는 왜냐하면 이 Caddy 서버는 내부 Caddy 서버로써 다른 Caddy 서버에서 reverse-proxy를 받는 서버이기에, 이 내부 Caddy 서버가 자동으로 SSL 인증서를 만드는 등의 일을 하지 않길 원하기 때문입니다. 오히려, 이런 reverse proxy 셋업에서 여러 레이어의 HTTPS를 설정해두는 것은 주로 나쁜 아이디어™이기에, 기본 HTTP 포트인 80에서 듣는 이유입니다.&lt;/p&gt;
&lt;p&gt;그리고, 여기에서 서비스들을 지칭할 때 &lt;code&gt;jenkins&lt;/code&gt;와 같이 호스트네임을 직접 사용하는 것을 눈치채셨을 수도 있습니다. 이는 위에서 말했던 커스텀 도커 네트워크가 사용되었을 때 작동하기 때문에, 서비스(예를 들어, Jenkins)와 내부 Caddy 서버 모두 같은 네트워크 상에 있어야 합니다! 이 경우에는, 내부 Caddy 서버를 &lt;code&gt;proxynetwork&lt;/code&gt;에 만든 후, 포트 80을 호스트 머신의 680 포트로 전달하겠습니다.&lt;/p&gt;
&lt;p&gt;이제 해야 할 부분은 두 서버들을 연결하는 것입니다!&lt;/p&gt;
&lt;h1 id=&quot;3부---두-서버-연결하기&quot;&gt;3부 - 두 서버 연결하기&lt;/h1&gt;
&lt;p&gt;외부 Caddy 서버에서 다음을 &lt;code&gt;Caddyfile&lt;/code&gt;에 추가합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# 내부 Caddy로 reverse-proxy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;timmy.private.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy 172.18.0.1:680&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;*.timmy.private.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy 172.18.0.1:680&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 내부 서비스들에 접속할 수 있습니다. 완성…?&lt;/p&gt;
&lt;p&gt;아직은 아닙니다!&lt;/p&gt;
&lt;h1 id=&quot;별로-안전하지-않기에&quot;&gt;별로 안전하지 않기에…&lt;/h1&gt;
&lt;p&gt;들 수 있는 생각이, &lt;code&gt;timmy.private.example.com&lt;/code&gt;가 VPN 네트워크 안 비공개 IP 주소로 가르키기에, 인터넷 상에서 이 서비스들에 접속할 수 없다고 생각하실 수도 있습니다.&lt;/p&gt;
&lt;p&gt;하지만 &lt;code&gt;host&lt;/code&gt; 키워드가 보이시나요? Caddy는 클라이언트가 무슨 호스트에 방문하는지 확인한 다음 적절한 블록으로 전달합니다. 그럼 Caddy가 확인하는 부분이 뭘까요? 바로 모든 HTTP/S 요청에 들어가는 &lt;code&gt;Host&lt;/code&gt; 헤더입니다.&lt;/p&gt;
&lt;p&gt;문제가 보이시나요? HTTP/S 요청들은 클라이언트들이 만드는데, 내용물을 마음대로 정할 수 있기에 문제가 됩니다.&lt;/p&gt;
&lt;p&gt;간단한 시험을 해보세요: VPN 네트워크에서 연결 해제한 후, 운영체제의 &lt;code&gt;hosts&lt;/code&gt; 파일을 연 후 &lt;code&gt;timmy.private.example.com&lt;/code&gt;를 서버의 공개 IP로 가리키는 줄을 추가하세요. 그런 다음 &lt;code&gt;timmy.private.example.com&lt;/code&gt;에 방문합니다. 무엇이 보이시나요?&lt;/p&gt;
&lt;p&gt;이 체크가 &lt;code&gt;hosts&lt;/code&gt; 파일과 같이 간단한 방법으로 우회 가능하기에, 안전하지 않고 조치가 필요합니다.&lt;/p&gt;
&lt;h1 id=&quot;4부---외부-레이어의-보안-강화&quot;&gt;4부 - 외부 레이어의 보안 강화&lt;/h1&gt;
&lt;p&gt;다시 외부 Caddy 서버 레이어에서, 다음을 추가합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(block_nonvpn) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    @notVPN not remote_ip 10.10.0.0/24&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    handle @notVPN {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        respond &quot;접근 금지&quot; 403&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때 &lt;code&gt;10.10.0.0/24&lt;/code&gt;를 VPN 네트워크의 실제 IP 레인지로 치환해야 합니다.&lt;/p&gt;
&lt;p&gt;여기에서 공개 IP가 비공개 서버 블록에 접근을 시도하는 것을 알아챘을때 곧바로 연결을 끊어버리는 방법을 사용하고 싶으실 수도 있습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(block_nonvpn) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    @notVPN not remote_ip 10.10.0.0/24&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    handle @notVPN {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        abort&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하지만, 이 방법은 권장되지 않는데, &lt;code&gt;fail2ban&lt;/code&gt; 등을 설정하여 연결 기록에서 403 응답이 많은 공격자들을 찾고, 네트워크에서 차단하여 로드를 감소시킬 수 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;물론, 이게 동작하려면 외부 Caddy 레이어가 &lt;code&gt;host&lt;/code&gt; 네트워크 모드여야 합니다. 제가 진행한 몇몇 실험에서, Caddy를 다른 네트워크 모드로 설정하게 된다면 모든 클라이언트들이 &lt;code&gt;172.18.0.1&lt;/code&gt;에서 오는 것으로 인식해버리게 되는데, 이는 도커 머신 호스트를 가리키는, 도커 네크워크 레이어의 내부 IP 주소입니다.&lt;/p&gt;
&lt;p&gt;그런 다음, 이 스니펫을 비공개 서비스들에 다음과 같이 추가합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# 내부 Caddy로 reverse-proxy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;timmy.private.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import block_nonvpn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy 172.18.0.1:680&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;*.timmy.private.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import block_nonvpn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy 172.18.0.1:680&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 다시 한번 &lt;code&gt;hosts&lt;/code&gt; 파일 시험을 해보면, 더 이상 작동하지 않는 것을 확인하실 수 있습니다!&lt;/p&gt;
&lt;h1 id=&quot;결론&quot;&gt;결론&lt;/h1&gt;
&lt;p&gt;도커 서비스들에 reverse proxy하기 위해 Caddy를 설정하는데 도움이 되었으면 합니다!&lt;/p&gt;
&lt;h1 id=&quot;도움을-주신-분들&quot;&gt;도움을 주신 분들&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://caddy.community/t/subdomains-separating-public-and-private-services/19331/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Caddy 포럼에서 보안에 대해 알려주신 @fvbommel님&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>애플 워치의 쓸데없는 손씻기 기능</title><link>https://ericswpark.com/ko/blog/2023/2023-02-04-handwashing-on-apple-watch-is-useless/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2023/2023-02-04-handwashing-on-apple-watch-is-useless/</guid><pubDate>Sat, 04 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;애플 워치에서 가장 실망스럽고 쓸데없는 기능: 손씻기 타이머랍니다. 꽤 아이러니하게도 2020년에 처음으로 발표되었을 당시, 저는 좋은 아이디어라고 생각했었지만요.&lt;/p&gt;
&lt;p&gt;문제: 작동 트리거…가 작동하지 않습니다.&lt;/p&gt;
&lt;p&gt;애플은 &lt;a href=&quot;https://support.apple.com/en-us/HT211206&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;트리거가 소리와 동작&lt;/a&gt;이라고 하는데, 아마도 물이 흐르는 소리를 들은 다음, 사용자가 손을 비비고 있는지 확인하는 것 같습니다.&lt;/p&gt;
&lt;p&gt;하지만 이게 안정작으로 작동하질 않습니다. 작동할 때는 마법처럼 느껴지지만, 거의 대부분의 경우에는 40초 동안 트리거를 작동시킬려고 손을 씻었음에도 불구하고 5초 정도밖에 손을 씻지 않았다고 인식하거나, 아예 트리거가 작동하지 않습니다.&lt;/p&gt;
&lt;p&gt;그리고 탐지를 잘못 하는 경우도 존재합니다: 물이 흐르는 소리가 들리면서 손을 문지르는 동작을 하면 발생하게 되는데, 예를 들자면 식기세척을 할 때 오탐지합니다.&lt;/p&gt;
&lt;p&gt;바보같은 제 생각: 애플 워치가 물이 흐르는 소리가 들리면, 사용자가 손을 씻고 있는지 먼저 묻습니다. 물론 손이 더러우면 애플 워치를 못 만지겠지만, 그럼 타이머를 시작하기 위해 사용자가 취할 수 있는 제스처 같은 동작을 구현하면 되죠. 예를 들어, 허공에 두 번 치는 동작을 감지하여 손전등을 키는 모토롤라 휴대폰처럼요.&lt;/p&gt;
&lt;p&gt;이렇게 하면 사용자가 손을 씻지 않을 때는 제스처를 취하지 않아 오탐지를 방지할 수 있고, 트리거가 흐르는 물의 소리만 감지하면 되기 때문에, 훨씬 더 간편해질 듯 합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;수정-2023-04-15&quot;&gt;수정 (2023-04-15)&lt;/h2&gt;
&lt;p&gt;엄청나게 많이 시도한 결과, 애플 워치의 손씻기 트리거가 정확히 뭔지 확인해볼 정도로 답답해졌습니다. 찾아보니, 물은 아닌 것 같더라고요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://techcrunch.com/2020/06/29/apple-began-work-on-the-watchs-hand-washing-feature-years-before-covid-19/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;TechCrunch&lt;/a&gt;의 브라이언 히터 (Brian Heater, &lt;a href=&quot;https://twitter.com/bheater&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;@bheater&lt;/a&gt;):&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;시스템은 머신 러닝 모델들을 사용하여 여러 방식들을 활용하는데, 워치의 마이크에서 추가적인 정보를 얻습니다. 모션과 더불어, 어플은 흐르는 물의 소리를 감지합니다. 하지만 그것도 충분하지 않은데, 최근에 더욱 인기가 높아진 이코-싱크들은 들을 수 있는 물 소리가 더욱 적기 때문입니다. 그래서 나머지 정보로는 비누를 문지르는 소리가 있죠. 특정할 수 있는 오디오 시그니쳐로 손씻기가 일어나고 있는지 확인할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;The system uses machine learning models to tackle different methods, but the system gets an additional nudge from the Watch’s microphone. Along with motion, the app listens for the sound of running water. Even that’s not enough, though — after all, eco sinks have become increasingly popular, meaning that there’s often less water sound to be listening for. The sound of squishing soap takes care of that last bit. It’s got a unique enough audio signature so as to confirm that handwashing is taking place.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.macstories.net/stories/watchos-7-the-macstories-review/#handwashing-detection&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;MacStories&lt;/a&gt;의 알렉스 구요트 (&lt;a href=&quot;https://www.macstories.net/author/alexguyot/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Alex Guyot&lt;/a&gt;):&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;손씻기 감지는 손목 움직임과 주변 소음 트래킹을 사용하여 사용자가 손을 씻는지 결정합니다. 애플에 의하면 비누를 문지르는 특정 소리가 탐지하는 지표 중 하나라고 합니다. (…)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Handwashing detection uses a combination of wrist movement and ambient sound tracking to make its determinations. Apple says that the unique squelching sound of soap is one of the main indicators. (…)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://edition.cnn.com/cnn-underscored/electronics/watch-os-7-preview-public-beta&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;CNN Underscored&lt;/a&gt;의 제이콥 크롤 (&lt;a href=&quot;https://www.cnn.com/profiles/jacob-krol&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Jacob Krol&lt;/a&gt;):&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;(…) 그리고 애플 워치는 이제 손씻기를 두 가지 방법으로 탐지하는데, 비누 펌프의 소리와 물, 그리고 손목 움직임을 감지하여 손을 20초 동안 씻는지 확인하고, 집에 돌아왔을 때 손을 씻도록 알림을 줍니다. (…)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;(…) And the Apple Watch will now track your hand-washing in two core ways: It will monitor and listen for the soap pump, as well as water and wrist movement, to ensure you’re washing your hands for 20 seconds, and will remind you to wash your hands when you return home. (…)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;생각해보면 꽤 당연한데, 만약 애플 워치가 물 소리만 탐지할 경우 물을 절약하기 위해 물을 끈 손씻기 세션은 감지하지 못하기 때문입니다. 그래서 손씻기 중 비누 소리를 최대한 내서 진행해봤는데, 감지율을 꽤 높인 것 같습니다. 실제로 감지율을 측정할 정도로 깊이 파고들진 않았는데, 추산해본다면 약 10%에서 70이나 80% 정도로 향상된 듯 합니다.&lt;/p&gt;</content:encoded></item><item><title>네이버의 허위 브라우저 광고 배너 없애버리기</title><link>https://ericswpark.com/ko/blog/2023/2023-01-24-get-rid-of-navers-fearmongering-browser-banner/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2023/2023-01-24-get-rid-of-navers-fearmongering-browser-banner/</guid><pubDate>Tue, 24 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;네이버, 이게 도대체 뭔가요?&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;naver-banner&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3360&quot; height=&quot;436&quot; src=&quot;/_astro/naver-banner.BAbbdOsB_ZaYelC.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;오래된 브라우저를 사용해서 뜨는게 아닙니다. 그냥, 네이버 쪽에서 개발한 브라우저인 웨일를 광고하고 있는 건데, 문제는 이 배너의 메시지가 현재 사용하는 브라우저가 보안에 취약하다고 사용자를 오해하게 만들기 때문입니다 (최신 버전의 파이어폭스(Firefox)와 사파리(Safari)에서도 배너가 뜨는 것을 확인했습니다).&lt;/p&gt;
&lt;p&gt;그런데, 네이버 웨일은 무슨 특별한 브라우저가 아닙니다. 그냥, 구글이 개발한 크로미움(Chromium, 크롬의 기반)에 네이버 자사의 스킨과 몇 가지 수정사항을 씌어 배포하는 것밖에 되지 않습니다. 이렇게 구글이 크로미움 버전을 새로 출시할 때마다 수정사항을 입혀 네이버가 다시 배포해야 되니 보안 업데이트는 오히려 네이버 웨일이 늦을 수도 있겠네요.&lt;/p&gt;
&lt;p&gt;그럼 이 배너는 어떻게 안 뜨게 할 수 있을까요? 그냥 현재 사용하는 브라우저의 광고 차단기로 없애버리면 됩니다. 차단해야 할 요소는 다음과 같습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;NM_TOP_BANNER&quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이걸 광고 차단 룰로 변경하면 (예시는 uBlock Origin):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;###NM_TOP_BANNER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt=&quot;ad-block&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1198&quot; height=&quot;586&quot; src=&quot;/_astro/ad-block.CUZ10aZP_Z1O4vIW.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이런 허위 광고는 안 했으면 좋겠습니다. 아니, 허위 광고는 처음부터 불법이지 않나요?&lt;/p&gt;</content:encoded></item><item><title>TextEdit이 SMB 서버에 이상한 폴더들을 남겨놓을때</title><link>https://ericswpark.com/ko/blog/2023/2023-01-23-textedit-leaving-behind-weird-folders-on-smb/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2023/2023-01-23-textedit-leaving-behind-weird-folders-on-smb/</guid><pubDate>Mon, 23 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;맥OS에서 TextEdit을 사용하여 SMB 서버에 텍스트 파일을 저장할 경우, 저장할 때마다 이런 폴더들을 남겨놓는 문제를 경험하셨을 수도 있습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;텍스트-파일-이름.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;텍스트-파일-이름.txt.sb-xxxxxxxx-xxxxxx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기에서 &lt;code&gt;x&lt;/code&gt;는 랜덤한 글자와 숫자를 나타냅니다.&lt;/p&gt;
&lt;p&gt;그리고 어쩔때는 TextEdit이 파일을 저장하는데 완전히 실패하는 경우도 있습니다.&lt;/p&gt;
&lt;p&gt;원인은 SMB 서버 상에서 닷파일(숨겨진 유닉스 파일)의 생성을 불허하는, 다음과 같은 설정을 사용하고 있어서 그럴 수도 있습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# 맥OS의 닷파일 없애기 (엿먹어라 맥OS)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;veto files = /._*/.DS_Store/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;맥OS의 닷파일을 불허하긴 하지만, TextEdit으로 저장하는 것 또한 망쳐놓습니다. 이유는 이러한 &lt;code&gt;.sb&lt;/code&gt; 폴더들이 &lt;code&gt;._텍스트-파일-이름.txt&lt;/code&gt; 파일들을 안에 가지고 있는데, 아마도 파일 속성을 저장하는데 사용되는 것 같습니다.&lt;/p&gt;
&lt;p&gt;TextEdit이 이렇게 동작하는 게 마음에 들지 않고, 기능을 꺼버리는 해결방안이 어딘가에 있겠며만, 이 서버는 (전자기기를 잘 못 만지는 사람들을 포함하여) 여러 사람들이 사용하기에 결국 설정값에서 위에 충돌하는 설정값을 지워서 해결했습니다. &lt;del&gt;망해라, 맥OS야&lt;/del&gt;&lt;/p&gt;</content:encoded></item><item><title>[수정: 사용하지 마세요] 아무 SMB 서버에서나 타임 머신 사용하기 (feat. 자동 마운트)</title><link>https://ericswpark.com/ko/blog/2023/2023-01-08-setting-up-time-machine-on-any-smb-server-feat-automount/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2023/2023-01-08-setting-up-time-machine-on-any-smb-server-feat-automount/</guid><pubDate>Sun, 08 Jan 2023 13:36:19 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;이 글 끝에 수정을 추가해두었습니다!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;맥OS의 타임 머신(Time Machine)은 기본으로 잘 하는 부분이 꽤 많습니다. 백업을 증감하는 방식으로 진행하기에, 백업 지정 장소에서의 디스크 용량을 많이 아낍니다. 자동으로 백그라운드에서 실행되며, 파워 냅(Power Nap)을 활성화해 둔다면 화면이 닫혀있는 상태에서도 백업이 진행되죠.&lt;/p&gt;
&lt;p&gt;못 하는 점은 딱 하나인데, 직접적으로 연결되어 있는 저장 장치가 아니면 백업을 끔찍하게 못합니다.&lt;/p&gt;
&lt;p&gt;원격 저장소로 백업하고 싶으시다고요? 다음 옵션 중 하나를 고르시면 됩니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;del&gt;애플에서 자동으로 다 해주는 기기를 구매하기&lt;/del&gt; 안 됩니다! “에어포트 (AirPort)” 라인업이라고 있었는데 몇백년전에 단종됐습니다.&lt;/li&gt;
&lt;li&gt;&lt;del&gt;AFP를 지원하는 NAS로 백업하기&lt;/del&gt; 안 됩니다! AFP도 역시 사라졌거든요. 만약 사용하고 계신 NAS가 AFP를 기반으로 한 타임 머신을 지원한다고 판매되었었다면, 사용하시는 NAS가 엄청나게 오래된 NAS이기 때문에 어제 내다버리시는 게 좋습니다.&lt;/li&gt;
&lt;li&gt;SMB를 지원하는 NAS를 구매한 다음, 타임 머신이 작동하기 위해 그 NAS가 “애플 확장들”(이게 뭔지는 잠시 후 설명드리겠습니다)을 지원하길 바래야 합니다. (그리고 나서 NAS 제조사가 삼바(Samba, SMB 서버 소프트웨어)를 끔찍하게 설정해서 안정적으로 작동하지 않으면 결국 창문 밖으로 NAS를 집어던지겠죠.)&lt;/li&gt;
&lt;li&gt;자작(DIY) NAS의 설정값을 며칠 간 건드리면서 작동하게 만듭니다. 이걸 제가 직접 해 봤습니다. 별로 좋지 않습니다. 아 방법을 선택하신 것을 후회하게 될 것입니다. (아, 그리고 아까 말씀드렸던 “애플 확장들”이 여기에 필요합니다. 그게 뭔지는 추후에 설명드리도록!)&lt;/li&gt;
&lt;li&gt;다 집어치우고 맥에서 우회해봅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;두 번째 방법부터 시작해서 설명드리겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;afp&quot;&gt;AFP?&lt;/h1&gt;
&lt;p&gt;AFP는 Apple Filing Protocol의 약자입니다. 엄청나게 오래된 프로토콜이기에 여기에서 설명드리지는 않겠습니다. 그리고 최신 맥OS 버전에서는 아예 지원이 삭제되었기에, 타임 머신을 위해 쓰고 싶으셔도 사용하실 수 없습니다. (강제로도요.)&lt;/p&gt;
&lt;h1 id=&quot;애플-확장&quot;&gt;”애플 확장?”&lt;/h1&gt;
&lt;p&gt;애플이 마이크로소프트가 개발한 SMB 프로토콜에 뭔갈 더하지 않는다면 애플이 아니겠죠. 그래서 애플 장치에서만 SMB 서버들이 잘 동작하게 추가 “확장”들을 개발해서 프로토콜에 추가했습니다.&lt;/p&gt;
&lt;p&gt;오픈 소스 SMB 프로젝트인 삼바(Samba)가 이러한 “애플 확장”들을 &lt;a href=&quot;https://www.mankier.com/8/vfs_fruit&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;vfs_fruit&lt;/code&gt;이란 이름의 모듈&lt;/a&gt;에 구현해두었는데, 모듈이 활성화된 상태에서도 타임 머신이 제대로 동작하지 않는 경우를 많이 겪었습니다.&lt;/p&gt;
&lt;p&gt;만약 “타임 머신 호환” NAS에서 갑자기 타임 머신이 작동을 멈춘 경우, 맥OS가 &lt;em&gt;딱&lt;/em&gt; 원하는 방식으로 맞추어진 &lt;code&gt;vfs_fruit&lt;/code&gt; 데이터를 받지 못해서 화내는 게 다반사일 겁니다.&lt;/p&gt;
&lt;h1 id=&quot;자작diy-nas&quot;&gt;자작(DIY) NAS?&lt;/h1&gt;
&lt;p&gt;그럼 만약 시놀로지(Synology)나 QNAP이 아닌, unRAID나 OpenMediaVault같은 소프트웨어가 돌아가는 자작 NAS를 기반으로 해서 타임 머신 서버를 만들면 어떨까요?&lt;/p&gt;
&lt;p&gt;꽤 머리가 아플 겁니다. 제 경우에는 unRAID NAS를 사용해서 타임 머신을 설정해보았습니다. 이론상으로는 곧바로 작동했어야 하지만, 백업들이 가끔씩 실패하였고, 서버를 같이 쓰는 가족도 비슷한 증상을 겪었습니다. 설정값을 건드리는데 몇주를 할애하였지만, 문제를 완전히 해결하지는 못했습니다. 어쩔때는 아무런 문제 없이 작동하다가, 1시간 정도 지난 후 아무런 설정값을 건드리지 않았는데도 실패하는 경우가 있었습니다.&lt;/p&gt;
&lt;p&gt;사람을 미치게 만들기 때문에 별로 추천드리지 않는 방법입니다.&lt;/p&gt;
&lt;h1 id=&quot;맥-우회법&quot;&gt;맥 우회법&lt;/h1&gt;
&lt;p&gt;지난 며칠 간 잘 작동하여, 결국 선택한 방법을 설명해드리겠습니다:&lt;/p&gt;
&lt;p&gt;SMB 서버에 저장된 이미지에 맥을 백업하도록 설정하는 겁니다.&lt;/p&gt;
&lt;p&gt;이제 어떻게 설정하는지 절차를 보여드리겠습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;서버에 연결합니다.&lt;/li&gt;
&lt;li&gt;디스크 유틸리티를 연 다음, 커맨드-N을 눌러 새로운 이미지를 만듭니다. 옵션값은 다음과 같이 설정합니다:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;이름: “Time Machine”&lt;/li&gt;
&lt;li&gt;크기: 원하시는 대로 설정하실 수 있지만, 맥의 드라이브 크기보다 크게 설정하시는 것을 추천합니다&lt;/li&gt;
&lt;li&gt;포맷: “APFS”&lt;/li&gt;
&lt;li&gt;암호화: “128비트 AES 암호화 (권장됨)” (비밀번호 선택창이 나옵니다)&lt;/li&gt;
&lt;li&gt;파티션: “단일 파티션 - GUID 파티션 맵”&lt;/li&gt;
&lt;li&gt;이미지 포맷: “분할 번들 디스크 이미지”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;disk-utility-image-creation&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1290&quot; height=&quot;940&quot; src=&quot;/_astro/disk-utility-image-creation.BIQqA2t__Z1lmvxc.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이 이미지를 “다운로드” 폴더와 같이 로컬에 저장하는 것이 매우 중요합니다! 서버에 바로 저장할 경우 다음과 같은 오류가 발생합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Operation failed with status 73: RPC version wrong&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(작업이 상태 코드 73으로 인하여 실패하였습니다: RPC 버전 틀림)&lt;/p&gt;
&lt;p&gt;이미지 이름은 아무렇게나 설정해도 괜찮습니다(글을 간결하게 적기 위해서 이제부터 분할 번들을 이미지로 지칭하겠습니다)만, 이미지를 구별할 수 있도록 맥의 이름으로 설정하시는 것을 추천합니다.&lt;/p&gt;
&lt;p&gt;생성이 완료되면, 맥OS가 이미지를 자동으로 마운트할 겁니다. 이미지를 추출한 다음, 서버로 이동시키세요.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;이미지를 마운트합니다. “키체인에 비밀번호 저장” 체크박스를 클릭하는 것을 잊지 마세요. 나중에 이미지를 자동으로 마운트하는데 중요합니다.&lt;/li&gt;
&lt;li&gt;사용하시는 터미널 어플에 “전체 디스크 접근” 권한을 활성화해주세요.&lt;/li&gt;
&lt;li&gt;터미널 어플에서 다음을 실행합니다:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sudo tmutil setdestination /Volumes/Time\ Machine&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;여기에서 &lt;code&gt;/Volumes/Time\ Machine&lt;/code&gt;을 실제 볼륨 이름으로 대체하는 것을 잊지 마세요!&lt;/strong&gt; 만약 불확실하시다면, 삭제하신 다음, (이미지가 아닌) 볼륨을 터미널 창으로 끌어오세요.&lt;/p&gt;
&lt;p&gt;실행한 다음 비밀번호를 입력하면 볼륨이 타임 머신 목적지로 설정됩니다. 데스크탑이 몇번 깜빡일 수도 있는데, 정상입니다.&lt;/p&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;타임 머신에서 자동 백업을 설정합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;참고: 어떤 경우에는 타임 머신이 스케줄을 자동으로 변경하는 것을 막을 수도 있습니다. 이 경우에는 “시스템 설정” 어플을 완전히 종료한 다음 다시 타임 머신 설정 페이지로 들어가 보세요.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;“옵션”을 선택한 다음, “백업 주기”를 “매시간마다”로 변경하세요:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;time-machine-automatic-schedule&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1002&quot; height=&quot;698&quot; src=&quot;/_astro/time-machine-automatic-schedule.DjcoSesO_Zz43tR.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;“배터리 전원에서 백업” 옵션은 꼭 매시간마다 백업이 필요하지 않으시다면 켜는 것을 추천드리지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;참고&lt;/strong&gt;: 만약 설정값이 계속 “수동”으로 변경된다면, 7번을 먼저 해보신 다음 다시 해보세요.&lt;/p&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;수동 백업을 실행합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;타입 머신 설정 안에서 목적지를 우클릭한 다음, “‘타임 머신’에 지금 백업”을 누릅니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;time-machine-manual-backup&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1054&quot; height=&quot;654&quot; src=&quot;/_astro/time-machine-manual-backup.DWbsC0km_1dBl9Y.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;만약 디스크가 “0 KB 사용가능”으로 표시되었다면 이 부분에서 해결될 겁니다.&lt;/p&gt;
&lt;ol start=&quot;8&quot;&gt;
&lt;li&gt;(선택, 권장됨) 자동 마운트를 설정합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;전 &lt;a href=&quot;https://apps.apple.com/kr/app/automounter/id1160435653?l=en&amp;#x26;mt=12&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;앱 스토어에 있는 유료 앱 “AutoMounter”&lt;/a&gt;을 결국에 사용하였는데, 다른 옵션들도 분명히 있을테니 찾아보시는 것을 추천합니다.&lt;/p&gt;
&lt;p&gt;앱에 SMB 서버를 추가한 다음, 공유 폴더가 마운트될 때마다 이 스크립트가 실행되게 합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 존재할 경우, 이전 타임 머신 볼륨 추출&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;diskutil&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; eject&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /Volumes/Time&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\ &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Machine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 맥OS 버그 때문에 10초 정도 기다리기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 디스크 이미지 파일 열기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /Volumes/smb-share/TimeMachine/Erics-MacBook-Pro.sparsebundle&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;경로들을 치환하는 것을 잊지 마세요.&lt;/p&gt;
&lt;p&gt;이제 타임 머신이 볼륨이 마운트되어 있는 것을 확인하면 자동으로 백업을 실행하고, AutoMounter가 SMB 공유 폴더가 사용 가능하다면 자동으로 볼륨을 마운트할 겁니다!&lt;/p&gt;
&lt;h1 id=&quot;결론&quot;&gt;결론&lt;/h1&gt;
&lt;p&gt;타임 머신을 사용하는게 이렇게 불편한게 믿겨지지 않습니다. 타임 머신에서 위의 절차들을 다 해주는 버튼이 있으면 좋겠지만, 지금으로선 이것도 나쁘지 않은 것 같습니다.&lt;/p&gt;
&lt;h1 id=&quot;업데이트-2023-01-26&quot;&gt;업데이트 (2023-01-26)&lt;/h1&gt;
&lt;p&gt;이 방식을 사용하지 마세요. 타임 머신이 네트워크 공유 폴더에 있는 분할 번들에 백업할 때, 네트워크에 부하를 꽤 많이 걸면서, 모든 것을 느리게 만듭니다. 아마도 패키지가 실제로는 크기가 작은 파일들을 많이 담고 있는 폴더라는 점과 연관이 있는 것 같습니다. 타임 머신이 애플의 SMB 확장으로 메타데이터를 불러오지 못하기 때문에 파일 전체를 다운받아 속도가 느려지는 것으로 추측합니다.&lt;/p&gt;
&lt;p&gt;이 시점에는 그냥 네트워크 방식의 타임 머신 백업을 포기했습니다. 그냥 외장 하드를 사용하세요: 아무리 멍청해도 쓰기 편하고 복원하기도 쉬우니까요.&lt;/p&gt;</content:encoded></item><item><title>안녕, 마스토돈!</title><link>https://ericswpark.com/ko/blog/2022/2022-12-16-hello-mastodon/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-12-16-hello-mastodon/</guid><pubDate>Fri, 16 Dec 2022 08:12:55 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;/ko/blog/2022/2022-11-26-goodbye-twitter&quot;&gt;트위터 계정을 삭제한 이후&lt;/a&gt;, 마스토돈이 뭔지, 어떻게 작동하는지 찾아보기 시작했습니다. 결과적으로, &lt;a href=&quot;https://tilde.zone/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;tilde.zone&lt;/a&gt;에 마스토돈 계정을 새로 열었습니다!&lt;/p&gt;
&lt;p&gt;계정 이름은 다음과 같습니다: &lt;a href=&quot;https://tilde.zone/@ericswpark&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;@ericswpark@tilde.zone&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;만약 계정을 새로 만든다면 한 가지 팁을 드리겠습니다. 제가 가장 헤맸던 부분은 “서버 선택하기” 단계였는데, 계정 생성이 이메일 계정이라고 생각하니 조금 더 이해하기 쉬웠습니다. 누군가가 이메일을 지메일에서 보내든, 야후에서 보내든, 개인이 호스팅하는 서버에서 보내든지 상관하지 않는 것처럼 말이죠. 그러니 서버를 고르실 땐 가장 괜찮다고 생각되는 컨텐츠 관리 정책을 가진 서버, 또한 관념/사상등이 자신과 가장 맞는 서버를 찾아서 계정을 만드세요! 그리고 “연합” (federation) 시스템의 덕분에 언제든지 다른 서버들의 사용자들을 연락할 수 있으니, 특정 서버에 계정을 만들 수 없어서 걱정하지 마세요.&lt;/p&gt;</content:encoded></item><item><title>ffmpeg: HDR 둥영상을 SDR로 변환</title><link>https://ericswpark.com/ko/blog/2022/2022-12-14-ffmpeg-convert-hdr-to-sdr/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-12-14-ffmpeg-convert-hdr-to-sdr/</guid><pubDate>Wed, 14 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;(모바일 사용자들을 위한 경고: 이 글은 파일 크기가 큰 사진들이 첨부되어 있기에 로딩이 느릴 수 있습니다. 사진을 압축/크기 변경하는 것을 고려했습니다만 사진 속 세세한 부분까지 보존하고 싶어 그대로 두었습니다. 만약 사진들이 로드되지 않는다면 컴퓨터에서 글을 읽어보세요. 감사합니다!)&lt;/p&gt;
&lt;p&gt;만약 저처럼 미디어를 수집하신다면, &lt;a href=&quot;https://en.wikipedia.org/wiki/High_dynamic_range&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;HDR (High Dynamic Range, 넓은 다이나믹 레인지)&lt;/a&gt;로 찍힌 영화나 둥영상과 같은 컨텐츠를 가지고 있으실 수 있습니다.&lt;/p&gt;
&lt;p&gt;그리고 많은 기기들을 보유하고 있으시다면, 거의 대부분의 최신 기기들은 HDR 컨텐츠를 아무 문제 없이 재생할 수 있다는 것을 아실 겁니다. 빠르게 참조하실 수 있도록, 인기가 높은 기기 라인업들이 언제부터 HDR을 지원하기 시작했는지 밑에 나열해두겠습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;애플
&lt;ul&gt;
&lt;li&gt;아이폰 8/8 플러스 이상&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;아이패드 9세대 이상&lt;/li&gt;
&lt;li&gt;아이패드 에어 4세대 이상&lt;/li&gt;
&lt;li&gt;아이패드 미니 6세대 이상&lt;/li&gt;
&lt;li&gt;전 아이패드 프로 모델 (1세대 12.9인치 모델 제외)&lt;/li&gt;
&lt;li&gt;애플 실리콘을 탑재한 맥&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;삼성
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://commercialmarineexpo.com/what-is-hdr-support-in-samsung-phone/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;갤럭시 S10/S10+/S10e 이상&lt;/a&gt;&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://commercialmarineexpo.com/what-is-hdr-support-in-samsung-phone/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;갤럭시 노트 10/10+ 이상&lt;/a&gt;&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot; id=&quot;user-content-fnref-2-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;추가적으로, HDR 재생을 지원하지 않는 기기에서는, 어떤 플랫폼과 둥영상 플레이어(VLC 등)는 HDR 둥영상을 SDR, 즉 Standard Dynamic Range (표준 다이나믹 레인지)로 “톤맵”(이게 무엇인지는 나중에 설명드리겠습니다)하는 것을 허용합니다. 이를 통해 하드웨어를 업그레이드할 필요 없이 HDR 컨텐츠를 재생할 수 있죠.&lt;/p&gt;
&lt;p&gt;아쉽게도, HDR 컨텐츠를 무슨 방식으로든 지원하지 않는 기기에서 재생을 시도할 경우 문제가 여럿 생기게 됩니다. 낡은 안드로이드 폰이나 태블릿, 낡은 애플 기기 등 HDR 톤매핑을 지원하는 외부 플레이어를 설치하는 것을 불허하는 기기에서 HDR 컨텐츠를 재생 시, 재생은 되겠지만 둥영상에 문제가 있다는 것을 느끼셨을 겁니다. 색상이 전부 색이 빠진 느낌이 들고, 밝기값이 틀릴 것입니다. 무엇이 문제일까요?&lt;/p&gt;
&lt;h1 id=&quot;색상-클리핑&quot;&gt;색상 클리핑&lt;/h1&gt;
&lt;p&gt;시작하기에 앞서, SDR 둥영상들이 사용하는 “색상 영역”을 확인하겠습니다. SDR 둥영상들은 대부분 Rec. 709 색상 영역을 사용하는데, 이 영역은 다음과 같습니다:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/e/ef/CIExy1931_Rec_709.svg&quot; alt=&quot;rec-709-diagram&quot;/&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt; 출처: &lt;a href=&quot;https://en.wikipedia.org/wiki/Rec._709&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Rec._709&lt;/a&gt; &amp;gt;&lt;/p&gt;
&lt;p&gt;이와 비교되게, HDR 둥영상들은 Rec. 2020 색상 영역을 사용하는데, 이 영역은 다음과 같습니다:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/b/b6/CIExy1931_Rec_2020.svg&quot; alt=&quot;rec-2020-diagram&quot;/&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt; 출처: &lt;a href=&quot;https://en.wikipedia.org/wiki/Rec._2020&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Rec._2020&lt;/a&gt; &amp;gt;&lt;/p&gt;
&lt;p&gt;콘 모양 안에 검정색으로 된 삼각형이 색상 스펙트럼에서 색상 영역이 나타낼 수 있는 부분을 표시합니다. 위에서 보실 수 있듯이, Rec. 2020이 Rec. 709보다 더 많은 색상을 나타낼 수 있습니다.&lt;/p&gt;
&lt;p&gt;이게 문제가 되는 이유를 설명드리자면, Rec. 2020 색상을 제대로 표시하기 위해 하드웨어 (기기와 화면), 플랫폼, 그리고 둥영상 플레이어가 모두 Rec. 2020을 표시하는 것을 (당연히) 지원해야 합니다. 톤매핑이 가능한 하드웨어-플랫폼-소프트웨어 조합에서는, Rec. 709 색상 영역 밖에 있는 색상들이 제대로 지원되는 영역 안으로 “매핑”된 색상 출력을 보실 수 있을 겁니다 (이게 앞서 말씀드린 “톤매핑”의 전부입니다).&lt;/p&gt;
&lt;p&gt;하지만 재생 조합(하드웨어-플랫폼-소프트웨어)이 톤매핑을 제대로 지원하지 않는다면, Rec. 709 영역 밖의 색상들은 지원되는 영역 안으로 강제로 맞추어지게 되고 (클리핑이라고도 불립니다), 결과적으로 표시되는 이미지는 회색빛을 띠는 부정확한 색상으로 표시되어 보기에 안 좋습니다.&lt;/p&gt;
&lt;p&gt;그러면 이 문제를 어떻게 해결할까요?&lt;/p&gt;
&lt;h1 id=&quot;해결-방안들&quot;&gt;해결 방안들&lt;/h1&gt;
&lt;p&gt;가장 좋은 순으로 몇 가지 해결 방안을 나열했습니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;HDR을 지원하는 기기로 업그레이드&lt;/li&gt;
&lt;li&gt;실시간 톤매핑을 제공하는 둥영상 플레이어를 사용&lt;/li&gt;
&lt;li&gt;컨텐츠가 SDR 버전으로 제공되는 경우 다시 받기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ffmpeg&lt;/code&gt;로 둥영상을 재인코딩&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이상적인 세상에서는, 저희 모두 HDR를 지원하는 기기를 사용하겠지만, 이 세상은 유토피아가 아니고 &lt;em&gt;지금 현재&lt;/em&gt; 출시되는 기기들 중에서도 HDR에 대한 지원을 부실하게 (또는 아예 안) 하는 경우가 많습니다. HDR 지원 기기만을 고르더라도, 이렇게 기기를 교체하는 것은 가격도 비싸고, 다른 해결 방안이 존재하는 한 예전 기기들을 교체할 충분한 이유가 되지 않습니다. (명확하게 말씀드리자면, 만약 기기를 업그레이드할 예정이었는데 HDR가 수많은 이유 중 하나였다면 업그레이드하셔도 괜챃습니다. 하지만 HDR를 지원하지 않는다는 이유만으로 기기를 교체하시는 경우, 거의 대부분의 기기들은 (특히 컴퓨터들) HDR를 지원하지 않기에, HDR 지원만으로 기기를 업그레이드하시는 것은 바람직하지 않을 겁니다.)&lt;/p&gt;
&lt;p&gt;다음으로 가장 좋은 방안은 톤매핑을 지원하는 둥영상 플레이어(예를 들어 VLC)를 사용하는 것입니다. 이는 다른 둥영상 플레이어가 탑재 가능한 데스크탑 운영체제에 적용 가능합니다. 하지만 만약 HDR 지원을 별도로 설치하는 것이 어렵거나 불가능한 기기를 사용한다면 어떻게 해야 할까요?&lt;/p&gt;
&lt;p&gt;만약 컨텐츠의 SDR 버전이 존재할 경우 이를 받으실 수도 있습니다. 이 방법을 사용하는 것이 대부분 쉽고 빠른데, 다운로드가 인코드보다 속도 면에서 빠르기 때문입니다. 하지만 HDR 버전만 존재한다면요?&lt;/p&gt;
&lt;p&gt;그렇다면 재인코딩밖에 답이 없습니다!&lt;/p&gt;
&lt;h1 id=&quot;시작하기-전에&quot;&gt;시작하기 전에&lt;/h1&gt;
&lt;p&gt;다음 섹션들에 작성된 HDR에서 SDR 변환 과정은 &lt;a href=&quot;https://stevens.li/guides/video/converting-hdr-to-sdr-with-ffmpeg/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;현재 삭제된 이 블로그 글&lt;/a&gt;(사이트 자체가 삭제된 듯 합니다)에서 발췌된 점을 빠르게 짚고 넘어가고 싶습니다. &lt;a href=&quot;https://web.archive.org/web/20180817195640/https://stevens.li/guides/video/converting-hdr-to-sdr-with-ffmpeg/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;아카이브된 버전을 Wayback Machine에서 확인하실 수 있습니다&lt;/a&gt;. 이 주제에 대한 블로그 글의 글쓴이, Daniel Stevens께 감사의 말씀 드립니다!&lt;/p&gt;
&lt;h1 id=&quot;원본&quot;&gt;원본&lt;/h1&gt;
&lt;p&gt;시연을 위해, &lt;a href=&quot;https://www.youtube.com/watch?v=mkggXE5e2yk&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;유튜브에서 가져온 이 둥영상&lt;/a&gt;을 사용하여 HDR에서 SDR로 변환하는 여러 방법을 보여드리려고 합니다. (둥영상을 첨부하기에는 파일이 너무 크기에, 둥영상 중앙에 있는 한 프레임을 기준으로 하겠습니다. 제가 하는 것을 따라서 해 보고 싶으시다면 둥영상을 다운 받으셔서 같은 명령들을 사용하시면 됩니다!)&lt;/p&gt;
&lt;p&gt;한 개의 HDR 프레임을 추출하여, 톤매핑 없이 Rec. 709에서 어떻게 보이는지 확인하겠습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vframes 1 original.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/original.CSQvmSQ2_1DHeHu.webp&quot; alt=&quot;original&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3840&quot; height=&quot;2160&quot;&gt;&lt;/p&gt;
&lt;p&gt;색이 빠진 느낌이 드는데, 예상하던 대로입니다. 그러면 이를 SDR로 제대로 변환하기 위해서는 무슨 명령을 실행해야 할까요?&lt;/p&gt;
&lt;h1 id=&quot;톤맵-알고리즘&quot;&gt;톤맵 알고리즘&lt;/h1&gt;
&lt;p&gt;다음은 사용 가능한 여러 톤맵 알고리즘입니다: &lt;code&gt;mobius&lt;/code&gt;, &lt;code&gt;hable&lt;/code&gt;, 그리고 &lt;code&gt;reinhard&lt;/code&gt;가 있습니다. 각각 확인해봅시다!&lt;/p&gt;
&lt;h1 id=&quot;mobius&quot;&gt;&lt;code&gt;mobius&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;똑같은 프레임을 추출한 다음 &lt;code&gt;mobius&lt;/code&gt; 톤맵 프리셋 적용:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=mobius,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -vframes 1 mobius.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결과물입니다:&lt;/p&gt;

&lt;img src=&quot;/_astro/mobius.D7kYdtKw.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Mobius 예시&quot;/&gt;
&lt;p&gt;색상이 더 보이는 듯 합니다! 하지만 아직도 뭔가 이상합니다. 색상이 너무 강한 듯 합니다.&lt;/p&gt;
&lt;p&gt;나머지 두 알고리즘을 사용해 보겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;hable&quot;&gt;&lt;code&gt;hable&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;명령:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -vframes 1 hable.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결과:&lt;/p&gt;

&lt;img src=&quot;/_astro/hable.BiAmz5eN.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Hable 예시&quot;/&gt;
&lt;p&gt;원했던 결과물에 꽤 가까운 듯 합니다… 그럼 비교 대상으로…&lt;/p&gt;
&lt;h1 id=&quot;reinhard&quot;&gt;&lt;code&gt;reinhard&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;명령:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=reinhard,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -vframes 1 reinhard.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결과:&lt;/p&gt;

&lt;img src=&quot;/_astro/reinhard.BzXjw3de.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard 예시&quot;/&gt;
&lt;p&gt;위의 사진과 &lt;code&gt;hable&lt;/code&gt;간의 차이를 확인하기 어려우실 수 있기에, 비교하는 도구를 밑에 만들어 두었습니다:&lt;/p&gt;
&lt;script type=&quot;module&quot; src=&quot;/home/runner/work/ericswpark.github.io/ericswpark.github.io/src/components/embeds/ImgComparison.astro?astro&amp;type=script&amp;index=0&amp;lang.ts&quot;&gt;&lt;/script&gt; &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/hable.BiAmz5eN.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Hable 예시&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/reinhard.BzXjw3de.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard 예시&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(왼쪽에 &lt;code&gt;hable&lt;/code&gt;, 오른쪽에 &lt;code&gt;reinhard&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;그리고 &lt;code&gt;mobius&lt;/code&gt;가 얼마나 채도가 높은지 확인하고 싶으시다면:&lt;/p&gt;
 &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/mobius.D7kYdtKw.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Mobius 예시&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/hable.BiAmz5eN.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Hable 예시&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(왼쪽에 &lt;code&gt;mobius&lt;/code&gt;, 오른쪽에 &lt;code&gt;hable&lt;/code&gt;)&lt;/p&gt;
 &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/mobius.D7kYdtKw.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Mobius 예시&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/reinhard.BzXjw3de.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard 예시&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(왼쪽에 &lt;code&gt;mobius&lt;/code&gt;, 오른쪽에 &lt;code&gt;reinhard&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;이제 사용 가능한 프리셋의 설명이 끝났으니, 각 프리셋 안에 조절 가능한 한 값에 대해 설명해 드리겠습니다. 그건 바로…&lt;/p&gt;
&lt;h1 id=&quot;채도-낮추기-desaturation&quot;&gt;채도 낮추기 (Desaturation)&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#Options-3&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;ffmpeg&lt;/code&gt;의 설명서&lt;/a&gt;에서:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;이 밝기 레벨을 초과하는 부분들의 채도를 낮춥니다. 값이 높을수록, 더 많은 색상 정보가 보존됩니다. 이 설정값은 밝은 부분의 색상이 부자연스럽게 날아가는 것을 방지하기 위해, 흰색으로 (부드럽게) 변환합니다. 이렇게 하면 범위를 벗어난 색상에 대한 정보를 줄이는 대신, 이미지가 더 자연스럽게 느껴집니다.&lt;/p&gt;
&lt;p&gt;기본값인 2.0은 보수적이며 대부분 하늘이나 직사광선이 그대로 비친 표면에 적용합니다. 0.0의 설정값은 이 기능을 비활성화합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이는 Daniel Stevens의 원글에서의 설명과도 일치합니다. 하지만, 그는 &lt;code&gt;0&lt;/code&gt; 설정값을 사용하여 이 옵션을 끄는 것을 추천합니다. 하지만 설명서는 “밝은 부분의 색상이 부자연스럽게 날아가는 것”을 방지하는 데 좋기에 이 기능을 켜두는 것이 좋은 것처럼 설명을 합니다.&lt;/p&gt;
&lt;p&gt;그러면 채도 낮추기 기능을 &lt;code&gt;0&lt;/code&gt;으로 설정한 &lt;code&gt;mobius&lt;/code&gt; 프레임을 생성한 후, 원래의 &lt;code&gt;mobius&lt;/code&gt; 사진(다시 설명드리지만, 기본값은 &lt;code&gt;2.0&lt;/code&gt;입니다. 만약 채도 낮추기 설정값을 적지 않았을 경우, &lt;code&gt;2.0&lt;/code&gt;으로 가정합니다)과 비교해 보겠습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=mobius:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -vframes 1 mobius-desat-0.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그냥 사진만을 표시하는 것은 별로 의미가 없기에, 다시 비교 슬라이더를 밑에 준비해두었습니다:&lt;/p&gt;

 &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/mobius.D7kYdtKw.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Mobius 예시&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/mobius-desat-0.COlVAZMK.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Mobius 채도 낮추기 설정값이 0으로 설정된 예시&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(왼쪽에 &lt;code&gt;mobius&lt;/code&gt;, 오른쪽에 채도 낮추기 설정값이 &lt;code&gt;0&lt;/code&gt;으로 설정된 &lt;code&gt;mobius&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;그리고 나머지들은 다음과 같습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -vframes 1 hable-desat-0.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=reinhard:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -vframes 1 reinhard-desat-0.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

 &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/hable.BiAmz5eN.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Hable 예시&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/hable-desat-0.Bxjh-wZS.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Hable 채도 낮추기 설정값이 0으로 설정된 예시&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(왼쪽에 &lt;code&gt;hable&lt;/code&gt;, 오른쪽에 채도 낮추기 설정값이 &lt;code&gt;0&lt;/code&gt;으로 설정된 &lt;code&gt;hable&lt;/code&gt;)&lt;/p&gt;

 &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/reinhard.BzXjw3de.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard 예시&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/reinhard-desat-0.DvIc95Rv.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard 채도 낮추기 설정값이 0으로 설정된 예시&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(왼쪽에 &lt;code&gt;reinhard&lt;/code&gt;, 오른쪽에 채도 낮추기 설정값이 &lt;code&gt;0&lt;/code&gt;으로 설정된 &lt;code&gt;reinhard&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;다른 값을 시도해보겠습니다. 저는 &lt;code&gt;reinhard&lt;/code&gt; 프리셋에서 &lt;code&gt;0.5&lt;/code&gt;로 한번 생성하겠습니다만, 만약 다른 프리셋을 원하신다면 명령에서 대치하셔서 실행하시면 됩니다. 그리고 비교 결과는 다음과 같습니다:&lt;/p&gt;

 &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/reinhard-desat-0.5.DozorF28.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard 채도 낮추기 설정값이 0.5으로 설정된 예시&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/reinhard.BzXjw3de.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard 예시&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(왼쪽에 채도 낮추기 설정값이 &lt;code&gt;0.5&lt;/code&gt;로 설정된 &lt;code&gt;reinhard&lt;/code&gt;, 오른쪽에 &lt;code&gt;reinhard&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;제가 생각하기에는 개인 취향에 따라 갈릴 것 같습니다. 만약 이 설정값이 왜 비활성화되어야 하는지 설명해주신다면 이 섹션을 수정하겠습니다.&lt;/p&gt;
&lt;p&gt;그러면, 무슨 프리셋을 사용하여 HDR 둥영상을 SDR로 변환해야 할까요?&lt;/p&gt;
&lt;h1 id=&quot;개인적인-비교와-생각들&quot;&gt;(개인적인) 비교와 생각들&lt;/h1&gt;
&lt;p&gt;위에 실시한 실험을 바탕으로 한 개인적인 생각들을 적어보겠습니다.&lt;/p&gt;
&lt;p&gt;일단 &lt;code&gt;mobius&lt;/code&gt; 알고리즘은 색상을 변조시키면서 너무 채도를 과하게 입히기 때문에 곧바로 고려 대상에서 제외했습니다. 만약 그렇게 색상이 화려한 것을 좋아하신다면 좋은 선택이 되실 수도 있지만, 제 느낌에는 둥영상의 색상을 너무 이상하게 바꾸기 때문에 사용하기 어려운 것 같습니다.&lt;/p&gt;
&lt;p&gt;그러면 &lt;code&gt;hable&lt;/code&gt;과 &lt;code&gt;reinhard&lt;/code&gt;가 남습니다. &lt;code&gt;reinhard&lt;/code&gt;는 밝은 사진을 생성하는데, (HDR과 SDR 버전이 둘 다 존재하는 컨텐츠를 사용하여 &lt;code&gt;ffmpeg&lt;/code&gt;의 출력과 비교하였을 때) &lt;code&gt;hable&lt;/code&gt;이 원본 SDR 소스와 가장 비슷했던 것 같습니다. 다시 설명드리지만, 개인 취향에 따라 갈릴 것 같습니다만, 저는 &lt;code&gt;hable&lt;/code&gt;이 잘 못하는, 어두운 장면이 잘 보이는 것을 선호하기에 &lt;code&gt;reinhard&lt;/code&gt;를 사용할 듯 합니다.&lt;/p&gt;
&lt;h1 id=&quot;사용-예시&quot;&gt;사용 예시&lt;/h1&gt;
&lt;p&gt;HDR에서 SDR 톤매핑을 적용한 소프트웨어들이 사용하는 알고리즘이 궁금하여 찾아본 결과 다음을 발견했습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스택 오버플로우(Stack Overflow)의 많은 코드 예시와 구현은 &lt;code&gt;hable&lt;/code&gt;과 채도 낮추기 설정값을 &lt;code&gt;0&lt;/code&gt;으로 설정한 것을 선호하였습니다.&lt;/li&gt;
&lt;li&gt;젤리핀(Jellyfin)에서는, &lt;a href=&quot;https://github.com/jellyfin/jellyfin/issues/415&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;HDR에서 SDR 톤매핑에 관한 기존 깃허브 이슈 쓰레드&lt;/a&gt;에서 많은 댓글이 &lt;code&gt;hable&lt;/code&gt;과 채도 낮추기 설정값을 &lt;code&gt;0&lt;/code&gt;으로 설정한 것을 사용한다고 얘기했습니다.&lt;/li&gt;
&lt;li&gt;젤리핀(Jellyfin)에서, &lt;a href=&quot;https://github.com/jellyfin/jellyfin/pull/3442&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;톤매핑을 적용한 pull request&lt;/a&gt;는 &lt;code&gt;reinhard&lt;/code&gt;를 기본값으로 지정하였으며, 채도 낮추기 설정값인 &lt;code&gt;desat&lt;/code&gt;을 &lt;code&gt;0&lt;/code&gt;과 &lt;code&gt;0.5&lt;/code&gt; 사이의 값을 추천한다는 문구가 적혀 있었습니다.&lt;/li&gt;
&lt;li&gt;앞서 제시했던 pull request에서, &lt;a href=&quot;https://github.com/jellyfin/jellyfin/pull/3442/files#r465143195&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;한 댓글이 왜 &lt;code&gt;hable&lt;/code&gt;이 사용되지 않았는지 질문하였는데&lt;/a&gt;, 젤리핀(Jellyfin) 개발자(였던 것 같습니다)가 &lt;a href=&quot;https://github.com/jellyfin/jellyfin/pull/3442/files#r467430384&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;hable&lt;/code&gt;이 &lt;code&gt;reinhard&lt;/code&gt;만큼 밝은 사진을 생성하지 않는다&lt;/a&gt;는 이유를 제시하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;실제로-둥영상-변환하기&quot;&gt;실제로 둥영상 변환하기&lt;/h1&gt;
&lt;p&gt;이제 선호하시는 알고리즘을 정하신 다음, 둥영상 전체를 변환하는 것은 다음과 같이 &lt;code&gt;-ss&lt;/code&gt;와 &lt;code&gt;-vframes&lt;/code&gt; 인수들을 제거한 후 사용할 인코더와 같은 인수들을 추가하는 것만큼 쉽습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=reinhard,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -c:v libx264 -preset veryfast -crf 18 -c:a aac -b:a 160k -movflags +faststart output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;결론&quot;&gt;결론&lt;/h1&gt;
&lt;p&gt;모든 상황에 적합한 HDR에서 SDR 톤매핑 해결 방안은 없지만, 위에 적어둔 실험과 설명들은 절차가 어떻게 되는지, 그리고 무엇을 해야 선호하는 알고리즘을 찾을 수 있는지에 대한 이해를 제공할 겁니다.&lt;/p&gt;
&lt;section data-footnotes class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;아이폰 8/8 플러스 이상의 모든 기종들은 HDR 재생을 지원하지만, 소프트웨어만을 고려하였을때로 한정됩니다. &lt;a href=&quot;https://mashable.com/article/netflix-hdr-not-real-iphone-8-plus&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;특정 아이폰(방금 말씀드린 아이폰 8/8 플러스와 “보급형”급 모델인 아이폰 SE 등)들은 하드웨어상에서 실제 HDR를 표시할 수 없습니다&lt;/a&gt;. &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;엄밀히 따지면, 삼성은 갤럭시 S8/S8+와 노트 8부터 “모바일 HDR” 재생을 지원했습니다. 하지만 HDR10+ 지원은 위에 명시된 기기부터 적용되었습니다. &lt;a href=&quot;#user-content-fnref-2&quot; data-footnote-backref aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-2-2&quot; data-footnote-backref aria-label=&quot;Back to reference 2-2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>ffmpeg: 둥영상을 정확한 프레임 단위로 자르기</title><link>https://ericswpark.com/ko/blog/2022/2022-12-03-ffmpeg-cut-videos-to-the-exact-frame/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-12-03-ffmpeg-cut-videos-to-the-exact-frame/</guid><pubDate>Sat, 03 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;둥영상들을 다음 명령으로 &lt;code&gt;ffmpeg&lt;/code&gt;으로 자를 때:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 -ss 00:00:24.183 -to 00:03:22.183 -c copy output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;어떤 경우에는 &lt;code&gt;ffmpeg&lt;/code&gt;가 정확한 프레임으로 자르지 않는 것을 보셨을 수도 있습니다. 지정된 시간인 &lt;code&gt;00:00:24.183&lt;/code&gt;보다 몇 프레임, 또는 몇 초 이후로 시작하는 경우입니다. 더욱 문제가 되는 점은, &lt;code&gt;ffmpeg&lt;/code&gt;가 마음대로 고른 프레임에 도달할 때까지 둥영상이 멈춰있는 것처럼 보이고, 도달한 이후에 영상이 재생된다는 점입니다. 무엇이 문제일까요?&lt;/p&gt;
&lt;p&gt;답은 (이전 &lt;code&gt;ffmpeg&lt;/code&gt; 관련 블로그 글들과 같이) 키프레임 타입에 있습니다. 간단히 설명하자면 특정 키프레임들(P-프레임과 B-프레임들)은 렌더링할 때 I-프레임을 필요로 합니다. 그래서 지정된 타임코드이 정확히 I-프레임에서 시작하지 않을 경우, &lt;code&gt;ffmpeg&lt;/code&gt;는 첫 I-프레임을 찾을 때까지 둥영상을 건너뛴 다음, 그 첫 I-프레임을 둥영상의 시작까지 반복합니다. 다음과 같이 말이죠:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;원본 파일:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;IPPPBIPPPBIPPPBIPPPB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       ^ 지정된 시작 타임코드&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg 시작 지점:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;----------IPPPBIPPPB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          ^&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;비어있는 프레임들을 ffmpeg가 채우는 방식:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-------IIIIPPPBIPPPB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;최종적으로 크기에 맞게 자르기:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;IIIIPPPBIPPPB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그러면 &lt;code&gt;ffmpeg&lt;/code&gt;가 시작 타임코드 이전에 있는 I-프레임으로 재인코딩하게 할 수는 없을까요? 원래 명령문에 &lt;code&gt;-c copy&lt;/code&gt; 옵션값을 보셨다면, 이 부분이 문제가 됩니다. 이 옵션값은 &lt;code&gt;ffmpeg&lt;/code&gt;에게 재인코딩 없이 둥영상을 그대로 복사하라는 지시를 하는데, 빠르긴 하지만 &lt;code&gt;ffmpeg&lt;/code&gt;가 프레임을 쌓아 재인코딩을 할 수 없게 만듭니다. 여기에서의 해결방안은 그냥 인코더를 사용하여 재인코딩을 가능하게 옵션값을 교체하는 것입니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 -ss 00:00:24.183 -to 00:03:22.183 -c:v libx264 -crf 18 output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>안녕, 트위터</title><link>https://ericswpark.com/ko/blog/2022/2022-11-26-goodbye-twitter/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-11-26-goodbye-twitter/</guid><pubDate>Sat, 26 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이 글이 게시될 쯤에는 &lt;a href=&quot;https://twitter.com/ericswpark&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;@ericswpark의 핸들을 가진 제 트위터 계정&lt;/a&gt;이 사라져 있을 겁니다. 만약 먼 미래에 이 글을 읽는다면, 봇이 핸들을 낚아챘을 수도 있죠.&lt;/p&gt;
&lt;p&gt;유명하지 않아 트위터에서 가짜 계정이 저를 사칭하지는 않겠지만, 혹시 몰라서 여기에 적습니다: 트위터 계정이 있지도 않고, 추후에 만들 계획도 없습니다. 만약 트위터에서 제 이름을 대는 계정이 있다면 무시하세요.&lt;/p&gt;
&lt;p&gt;공식적인 연락책은 사이트 네비게이션 바의 “정보” 섹션에서 찾아보실 수 있습니다.&lt;/p&gt;
&lt;p&gt;여담으로, 2011년 7월부터 가지고 있던 트위터 계정이네요. 안녕, 트위터.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;twitter-profile&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1200&quot; height=&quot;656&quot; src=&quot;/_astro/twitter-profile-ko.D5rWopq4_QHYa.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;업데이트-2022-12-16&quot;&gt;업데이트 (2022-12-16)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://reddit.com/r/RealTwitterAccounts/comments/zn4lnc/twitter_are_now_refusing_to_delete_suspended/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;트위터에서 계정 삭제를 막기 시작했다는 제보가 올라왔습니다&lt;/a&gt;. 아직까지도 계정이 있으시다면, 지금이 삭제를 (시도)할 좋은 기회일 수도 있겠네요.&lt;/p&gt;</content:encoded></item><item><title>부드러운 둥영상 탐색</title><link>https://ericswpark.com/ko/blog/2022/2022-11-07-smooth-scrubbing-videos/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-11-07-smooth-scrubbing-videos/</guid><pubDate>Mon, 07 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;수정&quot;&gt;수정&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/ko/blog/2023/2023-08-07-smooth-scrubbing-videos-version-2&quot;&gt;수정된 블로그 글을 여기&lt;/a&gt;서 찾아보실 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;둥영상을 재생할 때 어떤 둥영상 파일들은 재생 프로그렘에서 “탐색”이 부드럽게 되는 것을 눈치챘을 때가 있을 겁니다. 그러니까, “탐색”을 하기 위해 재생 위치 “헤드”를 좌우로 움직일 때, 끊김이나 별다른 “렉” 없이 프레임이 화면에 바로 표시되는 것 말입니다 (물론 재생하는 장치가 그만큼 빨리 재생할 수 있을 경우일 때를 가정합니다.)&lt;/p&gt;
&lt;p&gt;플레이어가 프레임 단위로 탐색하는 것뿐만 아니라, 어떤 둥영상들은 뒤로 탐색하는 것까지 허용합니다. 다시보기 같은 것을 분석하거나 둥영상에서 특정 부분을 찾을  매우 유용합니다.&lt;/p&gt;
&lt;p&gt;이 글에서는 왜 어떤 둥영상들이 이렇게 탐색이 간편한지 설명하고, 어떻게 둥영상을 재인코딩하여 탐색을 부드럽게 할 수 있는지 설명하겠습니다!&lt;/p&gt;
&lt;h1 id=&quot;둥영상-인코딩과-디코딩에-대한-간단한-정리&quot;&gt;둥영상 인코딩과 디코딩에 대한 간단한 정리&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;참고: 아래의 내용은 둥영상 인코더와 디코더가 어떻게 작동하는지를 간단하게 요약한 버전입니다. 이 블로그 글을 위해선 완전히 이해할 필요가 없지만 그래도 키프레임 (keyframe) 타입이 무엇인지 이해해야 다음 부분이 설명이 됩니다. 만약 전문가분께서 이 글을 읽으면서 너무 단순화되어 있다고 느끼신다면 죄송합니다!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;아시다시피, 둥영상 파일들은 단순한 컨테이너로, 한 개 또는 여러 개의 둥영상 “스트림” (stream), 한 개 또는 여러 개의 오디오 스트림, 그리고 다른 스트림 타입(예를 들어 자막 등)을 담고 있는 컨테이너입니다. 일단 오디오 스트림과 다른 스트림 타입은 중요하지 않으니 잠시 무시하겠습니다. 여기에서 집중해야 할 부분은 둥영상 스트림입니다.&lt;/p&gt;
&lt;p&gt;둥영상 스트림은 그냥 연속된 사진의 집합입니다. 하지만 사진들을 그대로 한 프레임씩 저장하는 것은 낭비가 꽤 심합니다. 다음과 같이 계산해보겠습니다!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한 픽셀은 빨간색, 초록색, 그리고 파란색 (RGB) 값으로 이루어져 있고, 각 값은 0에서 255까지의 범위 내에 있습니다 (이때 둥영상 색상이 8비트라고 가정하겠습니다).&lt;/li&gt;
&lt;li&gt;그러면 한 색상 당 8비트의 정보가 필요한데, 8비트는 1바이트이니 한 픽셀 당 3바이트의 값을 저장해야 됩니다.&lt;/li&gt;
&lt;li&gt;일반적인 1080p 둥영상 파일을 위해 1920 * 1080개의 픽셀을 저장해야 합니다. 1920 * 1080 = 2,073,600. 각 픽셀을 3바이트이니 한 프레임 당 6,220,800 바이트를 저장해야 됩니다. (약 6,221킬로바이트, 또는 6.2메가바이트의 데이타죠.)&lt;/li&gt;
&lt;li&gt;둥영상 파일이 1초당 24 프레임을 가지고 있다고 가정하겠습니다 (영화 등의 파일에서 흔한 값입니다). 6,220,800 * 24 = 149,299,200바이트, 또는 149,299킬로바이트, 또는 149메가바이트를 차지합니다. 1초밖에 안 되는 둥영상을 위해서 말이죠!&lt;/li&gt;
&lt;li&gt;약 30초 정도의 짧은 둥영상 파일이라고 가정해보겠습니다. 149,299,200 * 30 = 4,478,976,000바이트, 또는 4,478,976킬로바이트, 또는 4,479메가바이트, 또는 4.4기가바이트를 차지합니다.&lt;/li&gt;
&lt;li&gt;60초로 늘려보겠습니다. 149,299,200 * 60 = 8,957,952,000바이트 = 8,957,952킬로바이트 = 8,958메가바이트 = 9기가바이트를 차지하게 됩니다!&lt;/li&gt;
&lt;li&gt;2시간짜리 영화면 어떨까요? 149,299,200 * 60 * 60 * 2 = 1,074,954,240,000바이트 = 1,074,952,240킬로바이트 = 1,074,952메가바이트 = 1,075기가바이트 = 1 테라바이트를 차지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;당연하게도, 그 정도의 데이터를 저장하고 싶지는 않겠죠. 그래서 둥영상 인코더와 디코더가 필요한 겁니다.&lt;/p&gt;
&lt;p&gt;둥영상 스트림들은 여러가지 기준을 기반으로 인코딩되고 디코딩됩니다. 주로 사용되는 것이 H264이고, 그리고 그 뒤를 이어 표준이 되기 위해 경쟁하는 H265와 AV1 기준들이 있습니다. (물론, 다른 기준들도 여럿 존재합니다.) 둥영상 인코더의 역할은 둥영상의 프레임을 바탕으로 “키프레임”이라는 것을 생성하는 것입니다. 여기에서 키프레임은 세 가지 종류가 있는데, I-프레임, P-프레임, 그리고 B-프레임이 존재합니다.&lt;/p&gt;
&lt;p&gt;I-프레임은 예전에 설명했던 프레임 그대로입니다. 둥영상 프레임 하나를 구성하는 데이터 전부를 가지고 있습니다.&lt;/p&gt;
&lt;p&gt;P-프레임은 I-프레임을 참조합니다. 거의 대부분의 둥영상들은 패턴을 가지고 있는데 (예를 들어, 정적인 배경 앞에 동적인 사물/인물 등), 이 경우에는 둥영상 인코더가 장면을 모두 담고 있는 I-프레임을 만든 다음, ”둥영상 속 사물/인물이 오른쪽으로 2,000픽셀, 위로 400픽셀 움직였어“라는 내용의 P-프레임을 생성합니다. 그러면 디코더가 그 정보를 바탕으로 I-프레임 위에 바뀐 부분만을 다시 그리게 됩니다.&lt;/p&gt;
&lt;p&gt;B-프레임들은 앞과 뒤의 프레임을 모두 참조합니다. 가장 공간을 많이 절약하지만, 인코딩하고 디코딩하는데 부하가 가장 많이 필요합니다.&lt;/p&gt;
&lt;p&gt;그래서 위에 매우 간략하고 전반적인 둥영상 인코딩/디코딩 과정을 담았습니다. 그러면 이게 어떻게 둥영상 탐색에 영향을 미칠까요?&lt;/p&gt;
&lt;h1 id=&quot;키프레임과-둥영상-탐색&quot;&gt;키프레임과 둥영상 탐색&lt;/h1&gt;
&lt;p&gt;둥영상에서 앞과 뒤로 탐색하게 될 경우, 플레이어는 먼저 플레이헤드(탐색 위치 막대)에 가장 인접한 I-프레임을 찾아야 됩니다. 그 다음, I-프레임 위에 P-프레임과 B-프레임을 쌓아 원하는 장면까지 탐색하게 됩니다. (어떤 플레이어들은 그냥 이 과정을 다 무시하고 강제로 인접한 I-프레임으로 탐색합니다. 만약 플레이헤드가 놓은 위치에서 앞/뒤로 점프하는 것을 보셨다면 이를 경험한 것일 수도 있습니다.)&lt;/p&gt;
&lt;p&gt;이는 P-프레임과 B-프레임이 부분적인 프레임이기 때문입니다. 프레임 전의 I-프레임이 어떻게 바뀌었는지 설명하는 프레임에 불과하기 때문에, 그냥 그대로 표시할 수 없습니다. (전의 예시처럼, 만약 I-프레임이 ”파란색 하늘 배경에 하얀색 구름 앞 초록색 드레스를 입고 있는 사람을 그려“라는 명령을 담고 있다고 가정한다면, P와 B-프레임들은 ”초록색 드레스가 오른쪽으로 200픽셀 움직였어“와 같은 명령에 불과하기 때문입니다. 사람도 I-프레임의 명령 없이 P와 B-프레임의 명령을 정확히 그릴 수 없고, 컴퓨터도 마찬가지입니다.)&lt;/p&gt;
&lt;p&gt;기기들은 (적어도 최신 기기들은) I-프레임과 P-프레임을 비교적 빨리 디코딩할 수 있습니다. 따라서 둥영상이 I와 P-프레임으로만 이루어져 있다면, 탐색이 부드러울 것입니다. 하지만 B-프레임들은 더 많은 시간과 계산이 필요하기 때문에, 재생 막대를 빨리 탐색할 경우 기기들이 느려지거나 렉이 걸릴 수 있습니다.&lt;/p&gt;
&lt;p&gt;해결방안은 다음과 같습니다.&lt;/p&gt;
&lt;h1 id=&quot;b-프레임-없애기&quot;&gt;B-프레임 없애기!&lt;/h1&gt;
&lt;p&gt;둥영상을 재인코딩하여 B-프레임을 전부 없애버리면 됩니다! 다음 &lt;code&gt;ffmpeg&lt;/code&gt; 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i 입력-둥영상.mp4 -bf 0 출력-둥영상.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;참고로 사용된 인코더의 종류에 따라, 다른 옵션값을 사용해야 될 수도 있습니다. 예를 들어:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i 입력-둥영상.mp4 -c:v libx264 -x264-params bframes=0 출력-둥영상.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;탐색이-부드러운-둥영상들&quot;&gt;탐색이 부드러운 둥영상들&lt;/h1&gt;
&lt;p&gt;만약 탐색이 부드러운 둥영상을 재생해보고 싶고, 닌텐도 스위치를 가지고 있다면, 이것을 시도해보실 수 있습니다. 닌텐도 스위치 상 저장한 게임 캡쳐 영상은 전부 I와 P-프레임으로만 구성되어 있습니다. 한번 다른 기기로 옮겨서 재생과 탐색을 시도해보세요! 만약 재인코딩하는 것이 귀찮으시다면 부드러운 탐색을 경험해보실 수 있는 좋은 예시입니다.&lt;/p&gt;</content:encoded></item><item><title>제발 stale 봇을 잘못된 방식으로 사용하지 마세요</title><link>https://ericswpark.com/ko/blog/2022/2022-10-21-please-stop-using-the-stale-bot-incorrectly/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-10-21-please-stop-using-the-stale-bot-incorrectly/</guid><pubDate>Fri, 21 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;(번역 참고사항: 글 자체에 번역이 안된 영어 단어들은 개발자 용어로 딱히 한글 번역 문구가 존재하지 않는 단어들이라 그대로 두었습니다. 매끄럽게 번역되지 않은 부분 양해 부탁합니다. 만약 읽기 힘드시면 &lt;a href=&quot;/blog/2022/2022-10-21-please-stop-using-the-stale-bot-incorrectly&quot;&gt;영어 버전을 읽는 것을 추천합니다.&lt;/a&gt;)&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;깃허브의 여러 프로젝트들은 “깃허브 stale 봇”이라는 것을 사용하여 오랫동안 업데이트되지 않은, 즉 stale-한 issue (그리고 pull request)들을 닫아버립니다. 봇은 자동으로 issue들을 확인하고 댓글을 달아 issue가 아직도 관련성이 있는지 확인한 후, 리포지토리 주인이 설정한 시간값 안에 답글이 달리지 않을 경우 issue를 자동으로 닫아버리는 방식입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;이는 사용자와 개발자에게 매우 안 좋은 결과를 가져옵니다.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;오래된-issue들은-묻혀버립니다&quot;&gt;오래된 issue들은 묻혀버립니다&lt;/h1&gt;
&lt;p&gt;Issue 리포트는 말 그대로 치운다고 해서 사라지는 것이 아닙니다. Stale 봇을 사용하여 닫아버려도 프로젝트에서 버그가 사라지는 것은 아니니까요.&lt;/p&gt;
&lt;p&gt;사실, 프로젝트에 더욱 안 좋을 수도 있는데, 이제 버그의 관련된 모든 정보가 깃허브 Issue 탭의 “닫힘 (Closed)” 섹션으로 묻혀버리기 때문입니다. 사람들이 기존 issue를 찾지 못하면 뭘 할까요? 새로운 issue를 만들겠죠. 그러면 이제 해야 할 일은:&lt;/p&gt;
&lt;p&gt;가) 새로운 issue를 중복 항목으로 표기하고 예전 issue를 다시 열거나, 아니면&lt;/p&gt;
&lt;p&gt;나) 중복된 issue 글들을 병합하는 방법을 찾거나, 아니면&lt;/p&gt;
&lt;p&gt;다) 기존 issue에서 관련된 모든 정보를 새로운 issue로 복사/붙여넣기하게 만들어 사용자들을 화나게 만드는 것입니다.&lt;/p&gt;
&lt;p&gt;이어지는 것은…&lt;/p&gt;
&lt;h1 id=&quot;추가-일&quot;&gt;추가 일&lt;/h1&gt;
&lt;p&gt;Stale 봇은 개발자의 할 일을 줄여주기보다, 아이러니하게도 더욱 많은 할 일을 만들게 됩니다. Issue들이 묻히고 중복된 issue들이 새로 쌓이면서, 봇의 원래 목적의 &lt;strong&gt;정반대&lt;/strong&gt;를 달성하게 되는, 역효과를 불러옵니다. 따라서 코드를 작성하기보다 독자 여러분은 봇을 수시로 지켜보고 점검하며 프로젝트의 사용자들이 &lt;del&gt;깊은 빡침&lt;/del&gt; 화가 오를 때 이에 대응해야 하기도 합니다.&lt;/p&gt;
&lt;h1 id=&quot;지저분한-쓰레드&quot;&gt;지저분한 쓰레드&lt;/h1&gt;
&lt;p&gt;독자의 프로젝트의 사용자들이 활발하게 활동하며 issue가 아직도 관련성이 있다고 봇에게 수시로 답장하는 상황을 가정해보겠습니다. 봇이 활성화된 이상, 다음과 같은 대화가 issue 안에서 일어납니다:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Stale봇: Issue에 활동이 없는 것 같습니다. 닫을까요?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Stale봇이 &lt;code&gt;stale&lt;/code&gt; 태그를 추가했습니다.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;사용자: 아니요, 아직 관련성이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Stale봇이 &lt;code&gt;stale&lt;/code&gt; 태그를 제거했습니다.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(시간이 지난 후)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Stale봇: Issue에 활동이 없는 것 같습니다. 닫을까요?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Stale봇이 &lt;code&gt;stale&lt;/code&gt; 태그를 추가했습니다.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;사용자: 아니요… 아직 관련성이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Stale봇이 &lt;code&gt;stale&lt;/code&gt; 태그를 제거했습니다.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;개발자: 수정 작업을 하고 있는데, 지금은 의존성 X를 기다리고 있는 중이에요.&lt;/p&gt;
&lt;p&gt;Stale봇: Issue에 활동이 없는 것 같습니다. 닫을까요?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Stale봇이 &lt;code&gt;stale&lt;/code&gt; 태그를 추가했습니다.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;개발자: &lt;em&gt;죽여줘요&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Stale봇이 &lt;code&gt;stale&lt;/code&gt; 태그를 제거했습니다.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;봇 없는 같은 대화:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;개발자: 수정 작업을 하고 있는데, 지금은 의존성 X를 기다리고 있는 중이에요.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(시간이 지난 후)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;개발자: 의존 프로젝트가 버그를 수정해서, 이제 패치를 만들 수 있어요!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;개발자가 &lt;code&gt;feature-fix&lt;/code&gt; 브랜치를 병합했습니다.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;개발자가 완료 상태로 issue를 닫았습니다.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;개발자와 사용자의 입장에서 무슨 쓰레드가 선호도가 더 높을지 알 것 같습니다.&lt;/p&gt;
&lt;h1 id=&quot;하지만&quot;&gt;하지만…&lt;/h1&gt;
&lt;p&gt;물론, stale 봇을 위한 한 가지 사용처가 있다는 점은 인정하겠습니다. 사용자의 피드백을 기다리고 있을 때 말이죠.&lt;/p&gt;
&lt;p&gt;만약 사용자가 X 정도의 시간 안에 (여기에서 X는 독자의 참을성을 바탕으로 정합니다) 답장하지 않는다면, stale 봇이 issue를 닫더라도 괜찮다고 생각하는데, 제 생각에는 이런 경우 issue가 그 사용자에게 그렇게 중요하지 않았음을 짐작할 수 있는데, 만약 아니었다면 사용자가 추가적인 정보 (예를 들어 디버깅 로그 등)을 제공할 필요성을 느꼈을 것입니다.&lt;/p&gt;
&lt;p&gt;따라서 봇이 특정 태그 (예를 들어,  &lt;code&gt;awaiting-user-feedback&lt;/code&gt; (사용자 피드백 기다리는 중))를 지닌 issue만을 모니터링하게 만들어, 이 태그가 issue에 추가되었을 경우에만 카운트다운을 하게 만드세요.&lt;/p&gt;
&lt;p&gt;솔직히 stale 봇을 위한 유일한 사용 목적이라고 저는 생각하고, 실제로 “오래된” 버그 리포트들을 처리하는데 도움을 준다고 생각합니다.&lt;/p&gt;
&lt;h1 id=&quot;결론&quot;&gt;결론&lt;/h1&gt;
&lt;p&gt;특정 issue에 답글이 오랫동안 달리지 않더라도 issue가 오래된 것은 아닙니다! 제발 stale 봇을 잘못된 방식으로 사용하지 말아주세요. 프로젝트 사용자들 (그리고 개발자들)이 감사할 거예요. :)&lt;/p&gt;</content:encoded></item><item><title>UnRAID에서 패키지 수동으로 설치하기</title><link>https://ericswpark.com/ko/blog/2022/2022-09-24-manually-installing-packages-on-unraid/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-09-24-manually-installing-packages-on-unraid/</guid><pubDate>Sat, 24 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;수정&quot;&gt;수정&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/UnRAIDES/unRAID-NerdTools&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;UnRAID는 이제 NerdTools라는 플러그인이 있습니다&lt;/a&gt;. 기존의, 개발이 중단된 NerdPack 플러그인을 그대로 갖다 개발을 재시작한 것이라고 보면 되겠습니다. 한번 확인해보세요! 기존 글은 밑에 이어지지만 이제 오래되어 관련성이 없습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;오늘 홈 서버에 업데이트 (UnRAID 6.11.0)가 있는 것을 보고, 별 생각 없이 그냥 “업데이트” 버튼을 누르고 서버를 재부팅했습니다. 그런데 재부팅 후 관리 패털에 온라인으로 돌아오지 않자, 반시간 동안 마음 조리면서 복구 작업을 해야 했습니다.&lt;/p&gt;
&lt;p&gt;결국에는 뭐가 문제가 됐는지 발견했는데, UnRAID 6.11.x 버전을 기점으로, 너드팩 (NerdPack) 플러그인이 지원이 끊겨 업데이트 도중 자동으로 삭제되도록 되어 있었습니다. 너드팩에서 제공하는 패키지 중 &lt;code&gt;screen&lt;/code&gt; – 만약 모르신다면 이 패키지는 지속되는 터미널을 제공해줍니다 (물론 더 많은 사용처가 있을 수 있지만 전 이 용도로만 사용해서…) - 패키지가 특히 문제가 되었는데, 너드팩과 함께 제거가 되어 버렸습니다. 문제는, 테일스케일 (Tailscale, 기기 간 연결을 도와주는 VPN 플랫폼) 클라이언트 프로그램을 업데이트하고 실행하는 스크립트가 &lt;code&gt;screen&lt;/code&gt; 패키지에 의존했는데, 서버가 재부팅하면서 테일스케일에 연결을 하지 못하여 결국에는 원격으로 접속을 못 한 것입니다.&lt;/p&gt;
&lt;p&gt;그 문제를 해결하고 나서 어떻게 수동으로 UnRAID 상에 패키지를 설치하는지 요약해두고 싶었서 이 글을 작성하게 되었습니다. 물론 UnRAID 포럼 상 글과 댓글에 방법이 나열되어 있는데, 여러 글에 걸쳐 적혀 있어서 이 한 글로 요약해서 나중에 참고할 수 있도록 글을 작성합니다. 글을 작성하면서 참고했던 포럼 글과 댓글은 글의 밑에다가 적어두겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;unraid-베이스-버전-확인&quot;&gt;UnRAID 베이스 버전 확인&lt;/h1&gt;
&lt;p&gt;UnRAID는 슬랙웨어 (Slackware)를 기반으로 작성되었는데, 무슨 버전을 기반으로 했는지 확인하는 것이 중요합니다. 이를 알아내려면 다음 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cat /etc/slackware-version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다음과 같은 출력이 나와야 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Slackware 15.0+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;설치하고-싶은-패키지-찾기&quot;&gt;설치하고 싶은 패키지 찾기&lt;/h1&gt;
&lt;p&gt;이 페이지에서 슬랙웨어 15.0에 설치할 수 있는 패키지가 전부 나열되어 있습니다. (나중에는 위에서 찾았던 슬랙웨어 버전을 대치하는 것을 잊지 마세요.)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://slackware.pkgs.org/15.0/slackware-x86_64/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://slackware.pkgs.org/15.0/slackware-x86_64/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;패키지를 찾으면 링크를 눌러 다운로드 URL을 찾으세요. 예를 들어, &lt;code&gt;screen&lt;/code&gt;의 패키지 URL은 &lt;a href=&quot;https://slackware.pkgs.org/15.0/slackware-x86_64/screen-4.9.0-x86_64-1.txz.html&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://slackware.pkgs.org/15.0/slackware-x86_64/screen-4.9.0-x86_64-1.txz.html&lt;/a&gt; 였고, 안에 들어가서 다운로드 링크는 “바이너리 패키지 (Binary Package)“라는 글 옆에 적혀 있었습니다: &lt;a href=&quot;https://slackware.uk/slackware/slackware64-15.0/slackware64/ap/screen-4.9.0-x86_64-1.txz&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://slackware.uk/slackware/slackware64-15.0/slackware64/ap/screen-4.9.0-x86_64-1.txz&lt;/a&gt; . 이 URL을 클립보드에 복사하세요.&lt;/p&gt;
&lt;h1 id=&quot;unraid에-패키지-다운받기&quot;&gt;UnRAID에 패키지 다운받기&lt;/h1&gt;
&lt;p&gt;UnRAID 서버로 SSH (또는 mosh)를 하고 나서, &lt;code&gt;/boot/extra&lt;/code&gt; 경로로 진입하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ssh tower&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cd /boot/extra&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;두 번째 명령이 중요한데, 그 경로 안에 있는 모든 패키지는 UnRAID가 첫 부팅 시 자동으로 설치를 해주기 때문입니다.&lt;/p&gt;
&lt;p&gt;그런 다음, 패키지를 &lt;code&gt;wget&lt;/code&gt;으로 다운받으세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;wget &amp;#x3C;패키지 다운로드 URL&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예를 들어, &lt;code&gt;screen&lt;/code&gt;을 다운받으려면, 다음 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;wget https://slackware.uk/slackware/slackware64-15.0/slackware64/ap/screen-4.9.0-x86_64-1.txz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;재부팅-없이-패키지-바로-사용하기&quot;&gt;재부팅 없이 패키지 바로 사용하기&lt;/h1&gt;
&lt;p&gt;이제 UnRAID 서버를 재시작하면 패키지를 사용할 수 있습니다. 만약 재부팅하지 않고 바로 패키지를 사용하려면 다음 명령을 실행하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;installpkg &amp;#x3C;패키지&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예를 들어, &lt;code&gt;screen&lt;/code&gt;을 설치하려면, 다음 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;installpkg screen-4.9.0-x86_64-1.txz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때 패키지가 다운로드된 경로상에 있어야 합니다!&lt;/p&gt;
&lt;h1 id=&quot;참고-링크&quot;&gt;참고 링크&lt;/h1&gt;
&lt;p&gt;UnRAID 포럼 상 모든 유저분들과 개발자분들께 감사의 말씀을 드립니다! 이 글을 작성하면서 다음 글과 댓글들을 참고했습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://forums.unraid.net/topic/35866-unraid-6-nerdpack-cli-tools-iftop-iotop-screen-kbd-etc&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://forums.unraid.net/topic/35866-unraid-6-nerdpack-cli-tools-iftop-iotop-screen-kbd-etc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://forums.unraid.net/topic/35866-unraid-6-nerdpack-cli-tools-iftop-iotop-screen-kbd-etc/?do=findComment&amp;#x26;comment=1159381&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://forums.unraid.net/topic/35866-unraid-6-nerdpack-cli-tools-iftop-iotop-screen-kbd-etc/?do=findComment&amp;#x26;comment=1159381&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://forums.unraid.net/topic/35866-unraid-6-nerdpack-cli-tools-iftop-iotop-screen-kbd-etc/?do=findComment&amp;#x26;comment=1172107&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://forums.unraid.net/topic/35866-unraid-6-nerdpack-cli-tools-iftop-iotop-screen-kbd-etc/?do=findComment&amp;#x26;comment=1172107&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>GaN 충전기에서 충전이 자주 멈춘다면</title><link>https://ericswpark.com/ko/blog/2022/2022-09-23-frequent-disconnections-on-gan-chargers/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-09-23-frequent-disconnections-on-gan-chargers/</guid><pubDate>Fri, 23 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;오늘 노트북에서 작업을 하다가 휴대폰이 갑자기 충전 신호음을 몇 십초마다 반복하기 시작했습니다. 왜 그러는지는 이유가 명확하지 않았습니다. C-to-라이트닝 케이블은 상태가 괜찮았고, 충전기는 벽에 굳게 꼽혀 있었지만, 선들을 뽑았다 다시 연결하고 충전 포트에 먼지나 다른 물질이 안 끼어 있는 것을 확인했음에도 문제가 지속됐습니다.&lt;/p&gt;
&lt;p&gt;지목할만한 원인은 충전기 자체였는데, 올해 4월경 &lt;a href=&quot;https://www.11st.co.kr/products/4171195454?&amp;#x26;xfrom=&amp;#x26;xzone=&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;질화 갈륨 기술(gallium nitride, GaN)이 들어간 충전기&lt;/a&gt;를 새로 구입했는데, 200 W 출력으로 4개까지 기기를 충전할 수 있어 구매하게 되었습니다. (참고로 광고나 그런 글은 아닙니다! 후술할 주의사항이 글 상세정보란에 있어서 링크만 남겨둡니다.)&lt;/p&gt;
&lt;p&gt;처음에는 그냥 충전기가 불량인줄 알고, 교환이 가능한지 확인하기 위해서 주문 목록을 확인하던 중, 글 상세정보란에 뭔가 눈에 띄었습니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;사용시 주의점:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(...)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. 2개 이상의 포트를 연결하거나 출력값이 급격히 변동 시 디바이스 전력 분배를 위해 연결이 끊어졌다 재 충전을 진행할 수 있습니다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(...)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그제서야 문제를 알았습니다: 휴대폰과 노트북이 같은 GaN 충전기에 물려져 있었기 때문입니다. 노트북에서 전력 미터를 열어보니, 전력량이 사용에 따라 변화하는 것을 확인할 수 있었습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;power-meter&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1028&quot; height=&quot;850&quot; src=&quot;/_astro/power-meter.D4TpIKQL_CcaN.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;GaN 충전기는 노트북의 변화하는 전류 필요량에 따라 충전을 잠시 멈췄다가 시작하고 있었는데, 아이폰에서는 이를 충전기에서 연결이 해제된 줄 알고 몇초마다 충전을 멈췄다 시작했다를 반복하고 있었던 겁니다. 이것이 진짜 원인인지 확인하기 위해, 노트북을 충전기에서 분리하고 작업을 계속했더니, 문제가 다시 발생하지 않았습니다.&lt;/p&gt;
&lt;p&gt;임시적인 해결방안이요?&lt;/p&gt;
&lt;p&gt;음소거 스위치를 켜고, 책상에 뒤집어서 뒀습니다. 더 이상 방해가 되지 않도록.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;참고: 이게 GaN 충전기에만 국한된 문제가 아닐 수 있습니다. 원래 기술로 만들어진 충전기도 같은 문제가 있을 수 있는데, 제가 이 문제를 처음 접한 것은 GaN 충전기를 구매한 후이기 때문에 글을 올립니다.&lt;/p&gt;</content:encoded></item><item><title>유휴 타임아웃 때문에 SMART 테스트가 실패하는 것을 방지하기</title><link>https://ericswpark.com/ko/blog/2022/2022-09-17-keep-smart-tests-from-failing-from-idle-timeout/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-09-17-keep-smart-tests-from-failing-from-idle-timeout/</guid><pubDate>Sat, 17 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;만약 용량이 큰 하드 디스크에서 “연장”된 (extended) SMART 테스트를 진행한다면, 시간이 꽤 오래 걸릴 수 있습니다. 이때 운영체제가 활동이 없는 것을 감지한 후 하드 디스크의 헤드를 파킹하게 된다면 SMART 테스트가 도중에 중단됩니다.&lt;/p&gt;
&lt;p&gt;이렇게 중단된 SMART 테스트의 경우, &lt;code&gt;smartctl&lt;/code&gt;은 테스트가 아직 실행중이라고 보고하면서 다른 테스트를 실행하지 못하게 합니다. 하지만 &lt;code&gt;smartctl -a&lt;/code&gt;로 상태를 확인하면 테스트가 실행중이지 않은 상태로 표시됩니다.&lt;/p&gt;
&lt;p&gt;이런 오류 상태에서 벗어나려면, &lt;code&gt;smartctl -X&lt;/code&gt;를 실행해 테스트를 중단한 후, 다시  &lt;code&gt;smartctl -t long&lt;/code&gt; (디스크 매핑을 포함해서) 을 실행하여 연장된 SMART 테스트를 시작시킬 수 있습니다.&lt;/p&gt;
&lt;p&gt;그리고 이번에도 SMART 테스트가 유휴 타임아웃으로 인해 실패하는 것을 방지하려면, 다른 터미널에서 (&lt;code&gt;screen&lt;/code&gt;이나 &lt;code&gt;tmux&lt;/code&gt;가 유용할 수 있습니다) 다음 명령을 실행하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;watch -n 10 smartctl -a /dev/sda&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때 &lt;code&gt;/dev/sda&lt;/code&gt; 를 실제 디스크 매핑으로 변경하시는 것도 잊지 마세요!&lt;/p&gt;
&lt;p&gt;이 명령은 하드 디스크를 10초마다 확인하여 유휴 상태로 전환되는 것을 막아줍니다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;원래의 &lt;code&gt;watch&lt;/code&gt; 명령이 담긴 &lt;a href=&quot;https://www.reddit.com/r/DataHoarder/comments/jdgeat/comment/g9hw3mk/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;댓글&lt;/a&gt;의 작성자 &lt;a href=&quot;https://old.reddit.com/user/SI100km/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;u/SI100km&lt;/a&gt;께 감사의 말씀 드립니다.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>Jonsbo N1: 간단한 리뷰 글</title><link>https://ericswpark.com/ko/blog/2022/2022-07-04-jonsbo-n1-brief-text-review/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-07-04-jonsbo-n1-brief-text-review/</guid><pubDate>Mon, 04 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;홈 나스 (NAS)를 위해 Jonsbo N1을 사용해보고 간단하게 리뷰를 남겨봅니다.&lt;/p&gt;
&lt;h1 id=&quot;좋은-점&quot;&gt;좋은 점&lt;/h1&gt;
&lt;p&gt;예쁘고 책상 위에 알맞습니다.&lt;/p&gt;
&lt;h1 id=&quot;나쁜-점&quot;&gt;나쁜 점&lt;/h1&gt;
&lt;p&gt;케이스 안 팬이 약해서 하드 디스크들이 대부분 사용할 때 &lt;del&gt;48도&lt;/del&gt; 54도 (!) 를 넘어갑니다. 못 쓸 정도는 아니지만, 전 되도록이면 하드 디스크는 30도에서 40도 사이에 유지시키고 싶은데, 이 팬은 그러기에 충분하지 &lt;strong&gt;않습니다&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;좋은 소식은, 이 팬은 교체가 가능합니다 (아마도). 하지만 문제는 이 팬을 교체하더라도 케이스의 크기로 인해 이미 제약이 있기에 통풍이 나아질지 모르겠습니다. 만약 팬이 고장나면 교체 후 온도 문제가 해결되면 이 글을 수정하겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;끔찍한-점&quot;&gt;끔찍한 점&lt;/h1&gt;
&lt;p&gt;케이스의 구조와 디자인이 메인보드의 SATA 포트와 안 맞을 수 있는데, 적어도 제 메인보드와 호환되지 않았습니다. 그래서 4개의 SATA 포트 중 2개만 사용이 가능했습니다.&lt;/p&gt;
&lt;p&gt;호환이 되더라도, SATA 포트가 한 개 더 필요한데, 거의 대부분의 M-ITX 메인보드는 4개의 SATA 포트만을 제공하지만 N1은 디스크 베이가 5개가 있기 때문입니다. 만약 메인보드에 M.2 슬롯이 있다면, M.2에서 SATA로 변환해주는 카드를 넣어도 됩니다 (이때 M.2 슬롯이 허용하는 “키”를 잘 맞추어야 되는데, 제 메인보드에 경우에는 SATA 방식이 아닌, NVMe 방식의 “키”만 지원했습니다). 알리익스프레스에서 많은 SATA 컨트롤러 M.2 스틱을 파는데, 이게 데이터 무결성에 영향을 줄 지는 보장드릴 수 없기에 조심하시면서 사용하세요!&lt;/p&gt;
&lt;p&gt;아니면, LSI RAID 카드를 구입한 다음에 IT 모드로 플래시해서 사용하셔도 되는데, PCIe 포트를 하나 잡아먹게 됩니다.&lt;/p&gt;
&lt;h1 id=&quot;결론&quot;&gt;결론&lt;/h1&gt;
&lt;p&gt;다음 번에는 들고 다니기 힘들어도 아마 그냥 더 큰 케이스 안에서 나스를 제작할 것 같습니다. M-ITX 케이스에서 나스를 제작하는 건 비효율적인 것 같습니다. 물론, 일반적인 가정 사용 환경에서는 Jonsbo N1이 나스 용도로 사용하기에 좋은 M-ITX 케이스일 수 있지만, 몇 테라바이트 이상으로 저장된 데이터를 확장할 경우 다른 방법도 고민해보시는 것이 좋을 수 있습니다.&lt;/p&gt;
&lt;h1 id=&quot;수정&quot;&gt;수정&lt;/h1&gt;
&lt;h2 id=&quot;2022년-7월-14일&quot;&gt;2022년 7월 14일&lt;/h2&gt;
&lt;p&gt;Jonsbo 고객센터에 이메일을 보냈는데, 이렇게 답장이 왔습니다 (영어 원문, 한국어 번역본 아래):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Dear Eric,&lt;/p&gt;
&lt;p&gt;In summer time, the HDD temperature is usually a little bit higher, but usually if it is under 60℃ it should be okay.&lt;/p&gt;
&lt;p&gt;In any event, if you really want to change to use a more powerful fan, the standard is 14025, which mean 140 x 140 x 25 mm.
Noctua may be a good choice.&lt;/p&gt;
&lt;p&gt;I hope my information is useful for you.&lt;/p&gt;
&lt;p&gt;Have a good day.&lt;/p&gt;
&lt;p&gt;Best regards,
[name redacted]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;한국어 번역본:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;박선우께,
여름에는 하드 디스크의 온도가 조금 높을 수 있습니다만, 60도 아래로 유지가 된다면 괜찮습니다.&lt;/p&gt;
&lt;p&gt;어쨌든 더 강력한 팬으로 교체하고 싶으시다면, [케이스의 팬] 표준은 14025, 규격은 140 x 140 x 25 mm 입니다.
Noctua (해외 팬 제조사)가 좋은 선택일 수 있습니다.&lt;/p&gt;
&lt;p&gt;설명이 도움이 되었으면 합니다.&lt;/p&gt;
&lt;p&gt;좋은 하루 보내세요!&lt;/p&gt;
&lt;p&gt;감사합니다,
[이름 생략]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;만약 하드 중 하나가 열 때문에 죽으면 팬이랑 같이 교체하려고 합니다. 일반적으로 사용되는 규격을 Jonsbo에서 써서 다행입니다! 이 부분은 잘 선택했네요.&lt;/p&gt;</content:encoded></item><item><title>기가 지니가 TV를 바로 끄는 문제</title><link>https://ericswpark.com/ko/blog/2022/2022-06-25-giga-genie-turns-off-my-tv-problem/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-06-25-giga-genie-turns-off-my-tv-problem/</guid><pubDate>Sat, 25 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;오늘 집에서 TV가 이상하게 오작동하는 것을 고쳤는데, 아직도 왜 KT측 엔지니어들이 기능을 이렇게 고장난 상태로 구현하고 배포했는지 이해가 되질 않습니다.&lt;/p&gt;
&lt;p&gt;(이 글에 나오는 기가 지니는 기가 지니 3입니다. 구형 기기는 다른 문제가 있을 수 있습니다.)&lt;/p&gt;
&lt;h1 id=&quot;문제&quot;&gt;문제&lt;/h1&gt;
&lt;p&gt;TV를 키려고 기가 지니의 리모컨의 통합 전원 버튼을 누르면, TV가 켜졌다가 바로 꺼져버립니다. 이때, HDMI-CEC 동기화로 결국 기가 지니도 동시에 꺼져버리는데, 결국 무한 루프에 빠져 TV가 켜지지 않습니다.&lt;/p&gt;
&lt;p&gt;(간혹 HDMI-CEC의 동기화보다 더 빨리 기기를 킬 경우 어떻게든 TV와 기가 지니를 동시에 키는 방법이 있긴 있습니다만, 실험해 본 결과 5번 중 1번 꼴로 작동합니다.)&lt;/p&gt;
&lt;p&gt;이때, TV와 기가 지니가 켜질 때 자세히 보면 기가 지니에 시계가 나오는 LED 부분에 점 두 개가 찍혀 보입니다. (조그만 얼굴처럼 말이죠.) 그러다가 기가 지니가 켜지면 TV가 꺼집니다.&lt;/p&gt;
&lt;h1 id=&quot;해결-방법&quot;&gt;해결 방법&lt;/h1&gt;
&lt;p&gt;매우 간단합니다. 지니 설정에서 IR로 TV 제어, 또는 음성으로 IR 가전 제어 메뉴에서 “TV 제어” 옵션을 끄면 됩니다.&lt;/p&gt;
&lt;h1 id=&quot;원인&quot;&gt;원인&lt;/h1&gt;
&lt;p&gt;기가 지니가 연결된 TV가 켜지는 경우는 다음 세 가지입니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기가 지니 리모컨의 “통합 전원” 버튼을 누르거나, “TV 전원” 버튼을 눌렀을 때&lt;/li&gt;
&lt;li&gt;기가 지니와 TV 모두 HDMI-CEC로 연결되어 있는 상태에서 기가 지니의 전원을 켰을 때&lt;/li&gt;
&lt;li&gt;기가 지니 본체의 IR 발생기로 전원을 켜 달라고 요청할 때 (“지니야, TV 켜”)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;원래 예전에는 마지막 방식, 즉 IR 발생기는 음성 명령을 사용할 때만 동작했는데, 최근에 배포된 펌웨어 업데이트에서 이 기능이 고장난 것 같습니다. 이제는 기가 지니가 켜질 때 TV가 꺼져 있을 경우 IR을 통해서 TV를 키려고 하는데, 이때 이 버그가 발생하게 되는 것입니다.&lt;/p&gt;
&lt;p&gt;요약: 통합 전원으로 TV와 기가 지니 켬 -&gt; TV 켜짐 -&gt; 기가 지니가 켜지고 TV를 IR 발생기로 켬 -&gt; 기가 지니에서 나오는 신호를 받고 TV 꺼짐 -&gt; HDMI-CEC를 통해 TV가 꺼진 것을 확인한 기가 지니도 같이 꺼짐 -&gt; 무한 반복&lt;/p&gt;
&lt;p&gt;고쳐주세요 KT :(&lt;/p&gt;</content:encoded></item><item><title>UnRAID상에 드라이브에 쓰기 캐쉬 켜기</title><link>https://ericswpark.com/ko/blog/2022/2022-06-25-setting-write-cache-on-drives-on-unraid/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-06-25-setting-write-cache-on-drives-on-unraid/</guid><pubDate>Sat, 25 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;만약 “일반적인 문제 해결” (Fix Common Problems) 플러그인이 다음과 같은 경고를 주었었다면:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Write Cache is disabled on parity/disk1/diskX&lt;/p&gt;
&lt;p&gt;You may experience slow read/writes to parity/disk1/diskX. Write Cache should be enabled for better results. Read this post ( &lt;a href=&quot;https://forums.unraid.net/topic/46802-faq-for-unraid-v6/page/2/?tab=comments#comment-755621&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://forums.unraid.net/topic/46802-faq-for-unraid-v6/page/2/?tab=comments#comment-755621&lt;/a&gt; for more information. (…)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;링크를 방문하시면, &lt;code&gt;hdparm -W 1 /dev/sdX&lt;/code&gt;을 실행하고 성공적일 경우, 명령들을 “사용자 스크립트” (User Scripts) 플러그인에 넣고 스케줄러를 설정하여 디스크 배열이 처음으로 마운트될 때 실행되게 하라는 권고사항을 보셨을 겁니다.&lt;/p&gt;
&lt;p&gt;그럼 다음과 같은 스크립트를 작성하게 되겠죠:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;hdparm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -W&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/sdb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;hdparm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -W&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/sdc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;hdparm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -W&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/sdd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ... 생략 ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 스크립트는 두 가지 방법으로 개선할 수 있습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;똑같은 명령을 다른 값으로 돌리고 있으니, 배열에 모두 집어넣고 다음과 같이 순차적으로 실행시킬 수 있습니다:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;declare&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; drives&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;sdb&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;sdc&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;sdd&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 여기에 더 추가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; drive &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;drives&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;]}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    hdparm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -W&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$drive&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;/dev/sdX&lt;/code&gt;를 사용해서 디스크에 접근하지 마세요!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;왜 그렇냐고요? 왜냐하면 이는 영구적이지 않고, 리눅스가 원하는 대로 재부팅할 때마다 변경될 수 있으니까요!&lt;/p&gt;
&lt;p&gt;더 나은 방법은 드라이브의 ID를 활용하는 것입니다. UnRAID 서버에서 다음을 실행하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ls&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -al&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/disk/by-id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;ata&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -v&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;\-part&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 시스템 상의 드라이브가 모두 표시되는데, ID와 무슨 &lt;code&gt;/dev/sdX&lt;/code&gt;로 매핑되었는지 함께 표시됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 예시 ID 값들&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lrwxrwxrwx&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   9&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Jun&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 25&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 21:33&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ata-WDC_WD1234ABCD-567890_WJAN01234567&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ../../sdb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lrwxrwxrwx&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   9&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Jun&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 25&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 21:33&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ata-WDC_WD1234ABCD-567890_WJAN01234568&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ../../sdc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lrwxrwxrwx&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   9&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Jun&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 25&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 21:33&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ata-WDC_WD1234ABCD-567890_WJAN01234569&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ../../sdd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ... 생략 ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;스크립트를 다시 작성하면 다음과 같이 나오게 될 것입니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;declare&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; drives&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;WDC_WD1234ABCD-567890_WJAN01234567&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;WDC_WD1234ABCD-567890_WJAN01234568&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;WDC_WD1234ABCD-567890_WJAN01234569&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 여기에 더 추가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; drive &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;drives&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;]}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    hdparm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -W&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/disk/by-id/ata-&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$drive&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;“사용자 스크립트”에 추가하고 스케줄을 설정하면 됩니다!&lt;/p&gt;</content:encoded></item><item><title>임호화된 UnRAID 서버 자동으로 시작</title><link>https://ericswpark.com/ko/blog/2022/2022-06-22-unraid-encryption-auto-start/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-06-22-unraid-encryption-auto-start/</guid><pubDate>Wed, 22 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;암호화된 UnRAID 서버를 재부팅 후 자동으로 시작하는 방법을 찾는다면, 거의 대부분 &lt;a href=&quot;https://forums.unraid.net/topic/61973-encryption-and-auto-start/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;이 쓰레드에 있는 절차&lt;/a&gt; (경고: 영어)를 사용할 겁니다. 하지만 별다른 SMB 공유 폴더를 만들기 싫다면요? 비밀번호 파일을 계속 공유하는 서버를 운영하고 싶지 않다면 어떻게 할까요?&lt;/p&gt;
&lt;p&gt;그렇다면 사용하실 수 있는 다른 방법이 하나 있습니다.&lt;/p&gt;
&lt;h1 id=&quot;절차&quot;&gt;절차&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;UnRAID 서버가 있는 네트워크에 다른 컴퓨터에 다음 파이썬 스크립트를 만들고 실행시킵니다:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;main.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/usr/bin/env python3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; os.path &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; exists&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http.server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 여기에 설정값 수정&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 9999&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;PASSWORD_FILE_NAME&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;password.txt&quot;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # 현재 경로 안에&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;TIMEOUT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 300&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # 초 단위&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 비밀번호를 불러오거나 물어보기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; exists(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;PASSWORD_FILE_NAME&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;비밀번호 파일 &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{PASSWORD_FILE_NAME}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; (이)가 존재합니다. 안의 비밀번호를 사용합니다.&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    with&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; open&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;PASSWORD_FILE_NAME&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; password_file:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        PASSWORD_TO_SEND&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; password_file.readline().strip()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; getpass &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; getpass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;비밀번호 파일이 없습니다. 비밀번호를 입력하세요.&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    PASSWORD_TO_SEND&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; getpass()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 웹 서버 시작&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; RequestHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BaseHTTPRequestHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; do_GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        s.send_response(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        s.send_header(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;text/plain&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        s.end_headers()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        s.wfile.write(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.encode(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;PASSWORD_TO_SEND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;httpd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http.server.HTTPServer((&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), RequestHandler)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;httpd.timeout &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; TIMEOUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;httpd.handle_request()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;참고:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스크립트와 같은 경로에 &lt;code&gt;password.txt&lt;/code&gt; 파일을 만들고 안에 암호화 비밀번호를 저장하세요.&lt;/li&gt;
&lt;li&gt;타임아웃 값을 UnRAID 서버가 재부팅하는 시간보다 길게 설정하세요.&lt;/li&gt;
&lt;li&gt;다른 컴퓨터에 텍스트 파일로 비밀번호를 저장하지 않고 비밀번호를 수동으로 입력하고 싶으시다면, &lt;code&gt;password.txt&lt;/code&gt;을 삭제하시면 됩니다. 스크립트가 실행될 때 비밀번호를 물어봅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;UnRAID 플래시를 수정해서 다른 컴퓨터에서 키를 가져오도록 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(UnRAID 포럼에서의 &lt;a href=&quot;https://forums.unraid.net/topic/61973-encryption-and-auto-start/?do=findComment&amp;#x26;comment=648148&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;기존 스크립트&lt;/a&gt;를 제공하신 &lt;a href=&quot;https://forums.unraid.net/profile/2736-bonienl/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;@bonienl&lt;/a&gt;께 감사의 말씀 드립니다. 아래의 스크립트는 이 절차와 호환되도록 일부 수정된 버전입니다.)&lt;/p&gt;
&lt;p&gt;만약 다른 컴퓨터에 USB 스틱을 연결하였을 경우, 파일 경로에서 &lt;code&gt;/boot/&lt;/code&gt;를 빼셔도 좋습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/boot/config/go&lt;/code&gt;를 편집하고 다음 줄들을 추가/수정합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ...생략...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# emhttp 이벤트 경로에 스크립트 복사&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;install&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -D&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /boot/custom/keyscript/fetch_key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /usr/local/emhttp/webGui/event/starting/fetch_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;install&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -D&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /boot/custom/keyscript/delete_key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /usr/local/emhttp/webGui/event/started/delete_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 실행 권한 부여&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;chmod&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; a+x&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /usr/local/emhttpd/webGui/event/starting/fetch_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;chmod&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; a+x&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /usr/local/emhttpd/webGui/event/started/delete_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# webGUI 시작&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/usr/local/sbin/emhttp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/boot/custom/keyscript/fetch_key&lt;/code&gt;를 만듭니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [[ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -e&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; /root/keyfile ]]; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  curl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; other-computer:9999&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /root/keyfile&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # 포트를 변경하셨을 경우 여기에서 수정해주세요!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/boot/custom/keyscript/delete_key&lt;/code&gt;를 만듭니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;rm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /root/keyfile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;이제 UnRAID 머신을 재부팅시켜야 될 때, 다른 컴퓨터로 접속해서, 파이썬 스크립트를 실행시킨 다음, 재부팅을 진행하세요. 재부팅이 완료된 UnRAID 서버가 다른 컴퓨터에서 암호화 키를 가져와서 자동으로 디스크를 잠금 해제하고 마운트시킵니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 절차를 간단하게 하는 스크립트도 아래 적어두었습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 암호화 비밀번호 서버 스크립트 시작&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;python3&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; main.py&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 서버에 SSH로 접속 후 재부팅 시작&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 호스트 이름을 변경하는 것을 잊지 마세요&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ssh&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root@unraid-server-hostname&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reboot&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;문제-해결&quot;&gt;문제 해결&lt;/h1&gt;
&lt;h2 id=&quot;bonjour-문제&quot;&gt;Bonjour 문제&lt;/h2&gt;
&lt;p&gt;만약 UnRAID 서버나 다른 컴퓨터가 호스트를 찾을 수 없다는 오류를 발생시킨다면, 네트워크가 호스트 이름을 기반으로 서로를 찾는 것을 지원하지 않을 수 있습니다. 이 문제를 해결하려면, 두 머신 모두 고정 IP를 설정한 다음, 스크립트에서 호스트 이름을 고정 IP로 대체하세요.&lt;/p&gt;
&lt;h1 id=&quot;장점&quot;&gt;장점&lt;/h1&gt;
&lt;p&gt;다른 방식과 비교해서 이 방식은 몇 가지 장점을 가지고 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;비밀번호를 계속 노출시키는 서버와는 다르게, 로컬 네트워크에 잠시동안 비밀번호가 노출됩니다. 타임아웃 설정값을 넘을 경우, 간단한 HTTP 서버를 돌리는 스크립트는 자동으로 종료됩니다.&lt;/li&gt;
&lt;li&gt;만약 누군가가 UnRAID 서버에 몰래 접근하여 플래시 드라이브의 내용을 확인할 경우, 그들은 서버가 시작하기 위에 다른 컴퓨터에서 비밀번호를 가져온다는 것을 확인할 수 있지만, 비밀번호를 확인할 수는 없습니다 (단, 서버가 돌아가고 있을 경우에는 그냥 메모리를 확인하면 되기에 여기에서는 고려하지 않겠습니다).&lt;/li&gt;
&lt;li&gt;만약 누군가가 다른 컴퓨터에 몰래 접근할 경우, 그들은 UnRAID 서버에 비밀번호를 제공하는 스크립트가 있다는 것을 확인할 수 있지만, 비밀번호를 텍스트 파일로 저장하지 않았다면 확인할 수 없습니다. 그리고 만약 텍스트 파일로 저장되어 있다면, 다른 컴퓨터를 원격으로 잠그기만 하더라도 비밀번호를 확인할 수 없게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;단점&quot;&gt;단점&lt;/h1&gt;
&lt;p&gt;물론, 이 방식은 몇 가지 단점과 제약사항도 가지고 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이 방식에서는 다른 컴퓨터와 UnRAID 서버 사이의 통신이 암호화되어 있지 않습니다. 일반적으로 신뢰하는 기기들만이 있는 홈 네트워크에서는 문제가 되지 않지만, 만약 네트워크 상에 도청이 이루어질 경우 비밀번호를 가로챌 수 있습니다. 이 방식을 적용하기 전, 네트워크 보안을 검토하는 것을 고려하세요.&lt;/li&gt;
&lt;li&gt;이 방식은 UnRAID 머신과 같은 네트워크에 다른 컴퓨터가 있는 것을 필요로 합니다. 만약 UnRAID 서버가 예시로 DHCP 서버를 돌리고 있다면, 이 방식은 작동하지 않을 것입니다. 디스크가 마운트되고 서비스가 시작될 때까지는 UnRAID 서버가 다른 컴퓨터에 연락할 수 없기 때문입니다. 이 문제를 해결하려면 UnRAID 서버가 꺼져도 네트워크 쪽에 관여하지 않는 것을 확인하세요.&lt;/li&gt;
&lt;li&gt;비밀번호 파일은 다른 컴퓨터에 보호되지 않은 상태로 저장됩니다. 이 보안 리스크를 해소하려면 비밀번호를 텍스트 파일로 저장하는 대신, 스크립트를 돌릴 때마다 입력하실 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;결론&quot;&gt;결론&lt;/h1&gt;
&lt;p&gt;이 블로그 글을 작성하는 동안, &lt;em&gt;만약 UnRAID 서버와 같은 네트워크에 다른 컴퓨터가 있다면, 그냥 WebUI에 접속하여 비밀번호를 입력하면 되는 거 아니야?&lt;/em&gt; 라는 생각을 했습니다. &lt;em&gt;그럼 위의 방식은 별로 의미가 없는 거 아닌가요?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;하지만 이 방식은 다른 컴퓨터에 GUI 접근 (예를 들어, VNC/RDP 접근이 없는 헤드리스 (headless) 머신)에서도 작동합니다. 재부팅하기 전 이 스크립트를 돌리면 되기 때문입니다.&lt;/p&gt;
&lt;p&gt;보안상으로는 이 방식이 조금 취약할 수 있기에, 네트워크 보안과 개인의 위협 모델을 꼼꼼히 검토한 후 적용하시는 것을 추천합니다.&lt;/p&gt;</content:encoded></item><item><title>[수정: 막힘] Vivo 폰에서 다른 런처 쓰기</title><link>https://ericswpark.com/ko/blog/2022/2022-06-20-use-a-different-launcher-on-vivo-phones/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-06-20-use-a-different-launcher-on-vivo-phones/</guid><pubDate>Mon, 20 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;문제&quot;&gt;문제&lt;/h1&gt;
&lt;p&gt;Vivo 휴대폰들은 한 가지 이상한 제약사항과 함께 출고되는데, 기본 런처를 변경할 수 없게 되어 있습니다… Vivo 계정에 로그인하지 않으면 말이죠.&lt;/p&gt;
&lt;p&gt;기본 런처를 변경하는 것을 시도해 볼 순 있습니다. 하지만 메뉴가 변경을 허용하는 것처럼 보여도, 홈 버튼을 누르는 순간 기본 런처가 다시 Vivo의 런처로 리셋됩니다.&lt;/p&gt;
&lt;p&gt;…하지만 전 Vivo 계정을 만들기 싫고, Vivo에게 정보를 주는 것도 싫습니다. 버너 계정(가짜 정보로 만든 계정)을 만든다고 하더라도, 과정이 너무 귀찮습니다. &lt;em&gt;그냥 휴대폰이 다른 기본 런처를 받아들이도록 강제로 만들면 되잖아!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;(참고로 이 과정은 Vivo Y93에서 작동하는 것을 확인했는데, 이 폰이 제가 가지고 있는 유일한 Vivo 폰입니다. 다른 Vivo 폰에서는 작동하지 않을 수 있으며 실패할 경우 기기가 소프트브릭이 될 수 있기에 조심해서 진행하시고 모든 책임은 독자에게 있습니다!)&lt;/p&gt;
&lt;h1 id=&quot;해결-방안&quot;&gt;해결 방안&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;1:&lt;/strong&gt; 다른 런처(예를 들어, 노바 런처)를 일단 설치합니다. 저는 &lt;a href=&quot;apkmirror.com&quot;&gt;apkmirror.com&lt;/a&gt;에서 APK를 받아 ADB로 설치했는데, 중국 폰들은 구글 플레이 스토어가 없기 때문입니다. (물론 해 보면 당연히 올리는 방식이 있겠지만 구글 계정으로 로그인해야 되고, 이 기기 상에서는 별로 로그인하고 싶지가 않습니다.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2:&lt;/strong&gt; 개발자 옵션을 키고 다음을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;adb shell pm disable-user --user 0 com.bbk.launcher2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(참고: 만약 다른 Vivo 폰을 사용하시면 패키지 이름이 &lt;code&gt;com.bbk.launcher2&lt;/code&gt;와 다를 수 있습니다. 그리고 만약 새로운 폰에서 이 방식을 패치해두었을 경우 명령이 실패할 수도 있습니다. 이런 경우에는 어쩔 수 없이 다른 방안을 찾으셔야 합니다 :( )&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3:&lt;/strong&gt; 이 명령으로 Vivo의 기본 런처가 삭제되면 홈 버튼을 눌러 기본 런처를 설정하는 메뉴를 불러옵니다. 1번에서 설치했던 다른 런처를 고르고, “기본으로 설정” 체크상자가 체크되었는지 확인한 후 확인을 누릅니다.&lt;/p&gt;
&lt;p&gt;끝! &lt;del&gt;망해라 Vivo야&lt;/del&gt;&lt;/p&gt;
&lt;h1 id=&quot;수정&quot;&gt;수정&lt;/h1&gt;
&lt;p&gt;2022년 10월 21일을 기준으로 이 방안은 더 이상 동작하지 않습니다. 기본으로 탑재된 런처를 비활성화한다면 기본 런처를 선택하는 과정에서 무한 루프에 빠지게 되고, 폰은 사용 불가능 상태가 됩니다. 다시 원상 복구는 기본 탑재 런처를 &lt;code&gt;adb&lt;/code&gt;나 공장 초기화로 복원시켜놓는 수 밖에 없습니다.&lt;/p&gt;</content:encoded></item><item><title>macOS에 도커 (Docker) 제대로 설치하기</title><link>https://ericswpark.com/ko/blog/2022/2022-06-06-properly-install-docker-on-macos/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-06-06-properly-install-docker-on-macos/</guid><pubDate>Mon, 06 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;요약&lt;/strong&gt;: 다음을 실행하시면:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew install --cask docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;도커 데스크탑 (Docker Desktop)이 설치됩니다. 다른 방식은 &lt;em&gt;추천하지 않습니다.&lt;/em&gt; 자세한 정보는 아래를 참고하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;일단 제일 처음으로 다음 명령을 시도했는데:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew install docker docker-compose&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;설치가 잘 완료되어서 &lt;code&gt;docker --version&lt;/code&gt;과 &lt;code&gt;docker-compose --version&lt;/code&gt;을 돌려보니 둘 다 버전 정보를 정확하게 표시합니다.&lt;/p&gt;
&lt;p&gt;그래서 &lt;code&gt;docker-compose.yml&lt;/code&gt; 파일이 있는 폴더로 들어가서 서비스들을 &lt;code&gt;docker-compose up -d&lt;/code&gt;로 실행하려고 했는데, 도커에서 데몬 (daemon)을 찾을 수 없다고 경고해버리네요. 그래서 홈브루 (Homebrew)에서 활성화시킬 수 있는 서비스가 있는지, 또는 다른 방법으로 도커 데몬을 시작시킬 수 있는지 찾느라 거의 20분 가까이 낭비했습니다.&lt;/p&gt;
&lt;p&gt;알고 보니, &lt;code&gt;brew install docker&lt;/code&gt;는 도커 &lt;em&gt;클라이언트&lt;/em&gt;만 설치하고, 도커 엔진 (Docker Engine)과 다른 모든 부속품을 돌리기 위한 서버 서비스는 설치하지 않습니다. macOS에서는 이것이 아예 제공되지 않는데, 왜냐하면 macOS는 리눅스가 아니기 때문이죠! 리눅스가 가지고 있는 컨테이너 기술 (예를 들어, &lt;code&gt;cgroup&lt;/code&gt; 등)을 가지고 있지 않기 때문입니다. 도커 팀에서 제공한 우회 방안은 VM에 조그만 리눅스 이미지를 돌려 도커 클라이언트가 VM과 통신하는 방법입니다.&lt;/p&gt;
&lt;p&gt;그래서 다음 명령어들을 실행해야:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew install docker-machine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew install --cask virtualbox&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;도커 머신 스크립트와 버추얼박스 (Virtualbox)가 설치됩니다. (도커 머신 스크립트가 버추얼박스를 필요로 합니다.) 이때 시스템 재부팅이 필요한데, 버추얼박스가 아직도 커널 확장 프로그램 (kernel extensions)를 사용하기 때문입니다. 짜증나긴 하지만, 저장되지 않은 자료를 다 저장하고 한번 재부팅을 진행합니다.&lt;/p&gt;
&lt;p&gt;이제 도커 데몬이 돌아갈 수 있는 머신 (machine)을 만들어줍니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;docker-machine create --driver virtualbox default&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하지만 명령이 몇 분 후 &lt;code&gt;VBoxManage: error: Code E_ACCESSDENIED&lt;/code&gt;를 표시하고선 멈춰버렸습니다. 그래서 조사를 더 해보니, 도커 머신이 2019년 9월쯤 지원이 완전히 종료되었고, 더 이상 개발되지 않아 사용하면 안 된다고 합니다.&lt;/p&gt;
&lt;p&gt;그럼 이제 어떻게 해야 할까요? 도커 데스크탑을 사용하면 되는데, 홈브루의 캐스크 (Homebrew Cask)를 통해 설치할 수 있고, 확실하진 않지만 새로운 애플 실리콘을 탑재한 맥도 지원한다고 합니다.&lt;/p&gt;
&lt;p&gt;…지난 몇 시간 동안 저지른 쓸데없는 짓을 다 지워버리겠습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;docker-machine rm default   # `y`를 눌러 확인합니다&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;rm -rf .docker              # 삭제 전 .docker/ 안에 필요한 파일이 없는지 확인하세요!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew uninstall docker-machine docker docker-compose virtualbox&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그 다음 올바른 도커 데스크탑 캐스크를 설치합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew install --cask docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;도커 설치를 완료했지만, 사실 도커 데스크탑보다는 그냥 도커 엔진만 사용하고 싶었습니다. 왜나하면 도커 데스크탑은 독점 라이선스 아래 배포되고, 사용을 하려면 도커를 제작한 회사의 라이선스에 동의해야 됩니다. 도커 엔진만 무료로 사용할 수 있고 오픈 소스로 배포되지만, macOS에서는 사용할 수 없습니다.&lt;/p&gt;
&lt;p&gt;결국에는 macOS 개발 머신상에서는 도커 데스크탑을 사용하거나, 도커 컨테이너를 돌릴 수 있는 리눅스 인스턴스를 따로 만들어 사용하셔야 합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;수정: 이 글을 작성한 이후로 &lt;a href=&quot;https://github.com/abiosoft/colima&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;콜리마 프로젝트&lt;/a&gt;을 찾게 되었는데, QEMU를 사용하여 macOS에서 컨테이너 런타임을 제공하는데 목적을 둔다고 합니다. 아직은 완벽하지 않고 개발이 진행중인 프로젝트로서 &lt;del&gt;애플 실리콘 맥을 지원하지 않기에&lt;/del&gt; 이 글에서는 자세히 다루지 않겠습니다만, 염두해 두시는 것이 좋을 것 같습니다. 나중에 다듬어진 상태로 출시된다면 사용해 볼 생각입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;수정 2 (2025-11-01): Colima가 애플 실리콘 맥을 지원하도록 업데이트되었다는 제보를 독자분께 전달받았습니다! 알려주신 Wolf Eggers님께 감사의 말씀 드리겠습니다.&lt;/p&gt;</content:encoded></item><item><title>넷플릭스가 제 HDMI 동글을 싫어합니다</title><link>https://ericswpark.com/ko/blog/2022/2022-05-19-netflix-hates-my-hdmi-dongle/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2022/2022-05-19-netflix-hates-my-hdmi-dongle/</guid><pubDate>Thu, 19 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;만약 애플의 &lt;a href=&quot;https://www.apple.com/shop/product/MD826AM/A/lightning-digital-av-adapter&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Lightning Digital AV 어댑터&lt;/a&gt;를 이용해서 넷플릭스를 보려고 하신다면, 먼저 이 글을 읽으세요. 아예 재생조차 되지 않습니다:&lt;/p&gt;
&lt;p&gt;(여기에 원래 트윗이 있었는데 트위터 계정이 삭제되면서 없어졌습니다.)&lt;/p&gt;
&lt;p&gt;고마워요, 넷플릭스 DRM!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;수정&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;첫 트윗을 보내고 나서 넷플릭스 고객센터에서 답글이 몇 개 달렸는데, 아직까지 해결책은 나오지 않은 상태입니다 (2022년 5월 20일 기준). 트위터 DM으로 연락이 왔는데, 이 문제를 현재 확인중에 있고 제 버그 “리포트”를 “연구팀”에 넘긴 상태라고 합니다. 만약 추가로 답장이 온다면 이 글을 수정하겠습니다.&lt;/p&gt;</content:encoded></item><item><title>터미널 사생활 보호 모드</title><link>https://ericswpark.com/ko/blog/2021/2021-11-13-terminal-incognito-mode/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2021/2021-11-13-terminal-incognito-mode/</guid><pubDate>Sat, 13 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;어쩔 땐 터미널 기록에 저장되기 싫은 명령문을 실행해야 될 때가 있습니다. 예를 들어, GPG 키를 관리하는 명령어들은 기록에 저장되면 안 되겠죠. 만약 그렇다면 다음 스니펫이 도움이 될 수 있습니다.&lt;/p&gt;
&lt;p&gt;만약 &lt;code&gt;zsh&lt;/code&gt;를 사용하신다면 다음 코드를 &lt;code&gt;.zshrc&lt;/code&gt;에다, 아니면 &lt;code&gt;bash&lt;/code&gt;를 쓰신다면 &lt;code&gt;.bashrc&lt;/code&gt; (또는 &lt;code&gt;.bash_profile&lt;/code&gt;)에다가 삽입하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function incognito() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    unset HISTFILE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    export PROMPT=&apos;[INCOGNITO] %n@%m %1~ %# &apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;사용하시는 터미널에 따라서 &lt;code&gt;PROMPT&lt;/code&gt; 대신 &lt;code&gt;PS1&lt;/code&gt;을 설정하셔야 될 수도 있습니다. 여기에 표시한 &lt;code&gt;PS1&lt;/code&gt; 프롬프트는 맥OS의 기본 설정값에서 빌려 왔습니다. 필요하시다면 OS 기본 설정값이나 취향에 맞게 수정하세요.&lt;/p&gt;
&lt;p&gt;이제 &lt;code&gt;incognito&lt;/code&gt;를 입력하면, 현재 터미널 세션이 기록 파일에 저장되지 않습니다. 즐겁게 코딩하세요!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;이 방식이 어떻게 작동하는지 궁금하시다면, 꽤 간단합니다. 터미널은 기록 파일을 사용하여 사용 기록을 저장하는데, 예로 &lt;code&gt;.bash_history&lt;/code&gt;나 &lt;code&gt;.zsh_history&lt;/code&gt;에 이전에 사용했던 명령문을 저장하여 &lt;code&gt;history&lt;/code&gt; 명령이나 Ctrl-R 키(명령 기록 반전 탐색)로 검색하여 다시 불러올 수 있게 합니다. 이는 &lt;code&gt;HISTFILE&lt;/code&gt;이란 변수에 저장되는데, 이를 해제해주면 터미널이 이 기록 파일을 찾지 못하여 현재 세션이 저장되지 않습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;추가&quot;&gt;추가&lt;/h2&gt;
&lt;p&gt;만약 저처럼 &lt;a href=&quot;https://github.com/romkatv/powerlevel10k/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Powerlevel10k&lt;/a&gt;(ZSH 테마)를 쓰신다면, &lt;code&gt;export PROMPT&lt;/code&gt;를 사용한 프롬프트 수정이 작동하질 않습니다. 다행히도, 테마가 엄청 좋아서 커스텀으로 세그멘트를 프롬프트바에 추가하게 해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;~/.p10k.zsh&lt;/code&gt; 끝에 다음을 추가합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Custom incognito segment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function prompt_is_incognito_mode() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (( ! ${+HISTFILE} )); then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        p10k segment -f red -i &apos;🥷&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그 다음 &lt;code&gt;POWERLEVEL9K_LEFT_PROMPT_ELEMENTS&lt;/code&gt;를 정의하는 부분을 찾고, 맨 앞(이나 원하시는 곳 아무데나)에 &lt;code&gt;is_incognito_mode&lt;/code&gt;를 추가합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  # The list of segments shown on the left. Fill it with the most important segments.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # Incognito mode indicator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    is_incognito_mode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # =========================[ Line #1 ]=========================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # os_icon               # os identifier&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    dir                     # current directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    vcs                     # git status&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # =========================[ Line #2 ]=========================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    newline                 # \n&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    prompt_char             # prompt symbol&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고, &lt;code&gt;~/.zshrc&lt;/code&gt;안의 &lt;code&gt;incognito&lt;/code&gt; 함수를 이렇게 수정할 수도 있습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Function for &quot;incognito&quot; mode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function incognito() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    unset HISTFILE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    echo &quot;Done! This terminal session will not be recorded into history.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 &lt;code&gt;incognito&lt;/code&gt;를 실행하면 귀여운 닌자 녀석이 왼쪽에 나타납니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&gt; incognito&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Done! This terminal session will not be recorded into history.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; 🥷  | ~&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>리눅스에서 FUSE로 마운트할 때 특이사항 (VeraCrypt 포함)</title><link>https://ericswpark.com/ko/blog/2021/2021-11-10-fuse-mounting-quirks-on-linux-with-veracrypt/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2021/2021-11-10-fuse-mounting-quirks-on-linux-with-veracrypt/</guid><pubDate>Wed, 10 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;만약 리눅스에서 VeraCrypt 볼륨을 마운트할 때&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Permission denied: file.hc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;VeraCrypt::File::Open:232&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;와 같은 오류가 발생한다면, 이 글이 도움이 될 수 있습니다!&lt;/p&gt;
&lt;h1 id=&quot;왜-이런-오류가-발생하나요&quot;&gt;왜 이런 오류가 발생하나요?&lt;/h1&gt;
&lt;p&gt;거의 대부분 암호화된 VeraCrypt 파일이 VeraCrypt가 접근할 수 없는 FUSE 마운트에 저장되어 있기 때문에 그렇습니다.&lt;/p&gt;
&lt;p&gt;더 설명하자면, FUSE는 커스텀 파일시스템을 마운트할 때 사용되는데, 예를 들어 SSH를 기반으로 한 파일시스템이나 (SSHFS), 또는 삼바 (Samba) 공유 저장소 (NAS와 같은 저장소), 또는 다른 암호화 소프트웨어 (예를 들어 VeraCrypt의 또 다른 볼륨이나 Cryptomator)의 파일시스템이 여기에 해당합니다. FUSE는 마운트 장소를 지정하고, 사용자의 컴퓨터와 저장소 사이의 복잡한 번역을 담당하며, 사용자는 이러한 세부 사항을 모른 채 마운트를 변경할 수 있습니다.&lt;/p&gt;
&lt;p&gt;여기에서 문제가 왜 발생하냐면, 리눅스의 FUSE는 누가 처음으로 볼륨을 마운트했는지 확인하고 그 사용자 이외의 접근을 제한하기 때문입니다. 이는 적어도 기본 설정에서 슈퍼유저(최고관리자)에게도 해당합니다. 만약 이게 복잡하다면, 처음으로 파일시스템을 마운트한 사용자만이 그 파일시스템에 접근 권한이 있으며, 다른 아무도 이 마운트에 접근할 수 없다는 것을 기억하시면 됩니다.&lt;/p&gt;
&lt;p&gt;여기에서 VeraCrypt는 슈퍼유저 권한을 필요로 하는데, 그 이유는 볼륨을 &lt;code&gt;/mnt&lt;/code&gt;나 슈퍼유저 권한이 필요한 시스템 마운트 경로에 마운트를 시도하기 때문입니다. 따라서, &lt;code&gt;root&lt;/code&gt; (루트) 사용자로 실행하는데, 아까 위의 제한을 기억하시나요? 아무리 &lt;code&gt;root&lt;/code&gt; 사용자라도 FUSE는 암호화된 VeraCrypt 파일을 담은 기존 마운트의 접근을 허가하지 않기에 VeraCrypt가 이 파일에 접근하지 못하여 열지 못하는 것입니다.&lt;/p&gt;
&lt;h1 id=&quot;어떻게-해결하나요&quot;&gt;어떻게 해결하나요?&lt;/h1&gt;
&lt;p&gt;다음 방법 중 하나로 해결 가능합니다:&lt;/p&gt;
&lt;h2 id=&quot;root-사용자로-마운트하기&quot;&gt;&lt;code&gt;root&lt;/code&gt; 사용자로 마운트하기&lt;/h2&gt;
&lt;p&gt;기존의 파일시스템을 &lt;code&gt;root&lt;/code&gt; 사용자로 마운트한다면, VeraCrypt가 파일을 마운트하는데 아무런 문제가 없게 됩니다. 하지만, 이렇게 마운트를 한다면 기존 마운트에는 현재 사용자의 접근을 거부하게 되는데, 그러면 기존 마운트의 파일을 수정할 수 없게 됩니다. 만약 이 파일시스템이 NAS와 같은 파일을 수시로 수정해야 되는 마운트라면 이게 꽤 귀찮아집니다.&lt;/p&gt;
&lt;h2 id=&quot;fuse-설정으로-다른-사용자-또는-root를-허용하기&quot;&gt;FUSE 설정으로 다른 사용자 (또는 &lt;code&gt;root&lt;/code&gt;)를 허용하기&lt;/h2&gt;
&lt;p&gt;다음 줄을 &lt;code&gt;/etc/fuse.conf&lt;/code&gt; 파일에 추가합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;user_allow_other&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 줄이 이미 있다면, 앞에 코멘트가 없는 것을 확인해서 활성화 상태를 확인하세요.&lt;/p&gt;
&lt;p&gt;그 다음, 기존의 파일시스템을 마운트할 때, 선택적 명령 구문을 추가해서 다른 사용자 또는 &lt;code&gt;root&lt;/code&gt; 사용자가 마운트에 접근할 수 있도록 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;다른 사용자들의 접근을 허용하고 싶을 때 (&lt;code&gt;root&lt;/code&gt; 사용자 포함):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-o allow_other&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 &lt;code&gt;root&lt;/code&gt; 사용자만의 접근을 허용하고 싶다면:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-o allow_root&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>신뢰할 수 없는 장소에서 기기 충전하기</title><link>https://ericswpark.com/ko/blog/2021/2021-09-11-charging-from-untrusted-sources/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2021/2021-09-11-charging-from-untrusted-sources/</guid><pubDate>Sat, 11 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;공공 장소에서 종종 볼 수 있는 USB 포트에 대해서 생각해 본 적 있으세요? 예를 들어, 공항 의자 옆에 달려 있는 USB 포트나, 음식점 또는 커피샵의 테이블에 내장되어 있는 USB 포트 말입니다.&lt;/p&gt;
&lt;p&gt;이렇게 신뢰할 수 없는 USB 포트에 기기를 연결하는 것은 별로 좋은 생각이 아닙니다. 이런 플러그가 전원만을 공급하는지, 아니면 기기에서 정보를 탈취할 목적으로 회로가 내장되어 있는지 확인할 수 없기에, 이 문제를 해외 언론에서 &lt;a href=&quot;https://en.wikipedia.org/wiki/Juice_jacking&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;“juice jacking”&lt;/a&gt;이란 이름으로 경고했습니다. 이 문제 때문에 몇몇 분들은 &lt;a href=&quot;https://int3.cc/products/usbcondoms&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;USB 콘돔&lt;/a&gt;이란 장치를 구매해서 사용하기 시작했는데, 이 장치는 데이터 핀의 연결을 끊어두기 때문에 정보를 탈취할 수 없게 제작되어 있습니다.&lt;/p&gt;
&lt;p&gt;저도 처음에는 이게 꽤 괜찮은 기기로 생각하고 나중에 (코로나 방역조치가 완화되면) 나가서 돌아다닐 때를 대비해 구매할까 생각하다가, 사용성을 고려하면서 몇 가지 문제점을 발견했습니다. 왜 이런 장치들이 별로 도움이 되지 않는지를 다음 2가지 이유로 정리해 보겠습니다:&lt;/p&gt;
&lt;h1 id=&quot;1-데이터-핀-없이도-악의적인-플러그를-제작할-수-있음&quot;&gt;1. 데이터 핀 없이도 악의적인 플러그를 제작할 수 있음&lt;/h1&gt;
&lt;p&gt;위의 보호 장치로도 이 USB 플러그들이 기기를 손상시킬 수 있는 방법이 하나 있는데, 바로 USB 포트의 전원 핀을 통해 과도한 전압을 보내는 것입니다. 원리는 &lt;a href=&quot;https://en.wikipedia.org/wiki/USB_Killer&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;USB 킬러&lt;/a&gt; 장치와 비슷합니다.&lt;/p&gt;
&lt;p&gt;휴대폰들은 특정 전압을 받도록 설계되어 있는데, 예를 들면 5V나 (가장 흔함) 9V (고속 충전) 등을 받게 되어 있습니다. 따라서, 기기가 받을 수 있는 전압보다 더 많은 전압을 주입하면 기기를 죽이거나 충전 회로를 손상시킬 수 있습니다. 이런 전압은 전원 핀으로 보내기에, USB 콘돔을 연결해도 별 소용이 없는데, 왜냐하면 USB 콘돔을 데이터 핀만 연결 해제하기 때문입니다.&lt;/p&gt;
&lt;p&gt;물론, 가까운 커피샵의 플러그가 이렇게 악의적으로 제작되었을 확률은 매우 낮지만, 이 글은 신뢰할 수 없는 &lt;strong&gt;아무런&lt;/strong&gt; 장소에서 충전하는 것을 다루고 있기에 누군가 이렇게 극으로 악의적인 충전기를 설치했다는 가능성을 포함해서 작성되었습니다. 이런 경우에는 USB 콘돔이 서술했듯이 아무런 도움이 되질 않습니다.&lt;/p&gt;
&lt;h1 id=&quot;2-고속-충전이-비활성화됨&quot;&gt;2. 고속 충전이 비활성화됨&lt;/h1&gt;
&lt;p&gt;최근 기기는 데이터 핀에 정보를 주고받으며 고속 충전을 구현합니다. 제대로 기억한다면 애플 기기들은 데이터 핀 사이에 저항을 둔 다음 기기에서 저항값을 읽어 얼마만큼의 전류를 출력할 수 있는지 확인하고, 안드로이드 기기들은 여러 가지 프로토콜을 사용해서 (예를 들어, 퀄콤의 퀵차지 기술) 고속 충전을 구현하는데, 이 블로그 글이 길어지는 것을 막기 위해 여기서는 더 자세히 다루지는 않겠습니다.&lt;/p&gt;
&lt;p&gt;하지만 모두 공통적으로 USB 포트의 데이터 핀을 사용하는데, USB 콘돔은 이 데이터 핀을 끊어두기에 기기들이 저속으로 충전하게 됩니다.&lt;/p&gt;
&lt;h1 id=&quot;해결책은&quot;&gt;해결책은?&lt;/h1&gt;
&lt;p&gt;들고 온 USB 충전기를 사용하거나, 작은 보조배터리를 조절하는 용도로 사용합니다.&lt;/p&gt;
&lt;p&gt;일반 전원플러그는 당연히 정보를 탈취할 수 없고, 전압을 과도하게 높게 보내도 죽는 건 폰이 아니라 USB 충전기 뿐입니다. (물론, 여기에선 USB 충전기가 인지도 있는 회사에서 퓨즈와 같은 안전 장치를 다 갖추고 있다는 것을 조건으로 합니다!) 고장난 폰을 교체하는 것보다 고장난 USB 충전기 하나만을 교체하는 것이 당연히 더 저렴합니다.&lt;/p&gt;
&lt;p&gt;만약 USB 충전기를 구매하고 싶지 않거나, USB 포트만을 제공하는 환경에 있다면, 보조배터리를 중개 용도로 사용하는 방법도 있습니다. 보조배터리를 신뢰할 수 없는 충전기에서 충전한 다음, 폰을 보조배터리를 사용해 충전하는 것입니다. 여기에서 보조배터리는 전원과 기기 사이의 조절 역활만 하기 때문에 그렇게 큰 용량을 가진 무거운 보조배터리를 구매할 필요도 없습니다.&lt;/p&gt;</content:encoded></item><item><title>장고: 개발 도중 커스텀 유저 모델로 변경하기</title><link>https://ericswpark.com/ko/blog/2021/2021-07-13-django-switch-to-custom-user-model-mid-project/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2021/2021-07-13-django-switch-to-custom-user-model-mid-project/</guid><pubDate>Tue, 13 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이 글은 어떻게 현재 사용되는 장고 (Django) 프로젝트에서 데이터를 잃지 않고 커스텀 유저 모델로 변경할 수 있는지를 다룹니다.&lt;/p&gt;
&lt;p&gt;저도 커스텀 유저 모델이 필요가 없어도 꼭 써야 된다는 것을 모르고 프로젝트를 개발하다가 이 절차를 따라야 했습니다. 사실 &lt;a href=&quot;https://docs.djangoproject.com/en/3.2/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;장고의 사용 설명 문서에서도 이를 권장하는데&lt;/a&gt;, 이 경고는 빠른 시작 가이드나 튜토리얼에선 찾을 수가 없어 처음부터 따라야 하는지도 몰랐습니다. 아마도 설명 문서가 이를 조금 더 강조했다면 좋지 않았을까요?&lt;/p&gt;
&lt;p&gt;어쨌든, 이와 같은 상황이라면 이 글에 적힌 방법으로 문제를 해결할 수 있습니다.&lt;/p&gt;
&lt;h1 id=&quot;경고&quot;&gt;경고&lt;/h1&gt;
&lt;p&gt;언제나 데이터베이스를 백업해두어, 문제가 생기면 바로 원상복구할 수 있도록 준비하세요!&lt;/p&gt;
&lt;h1 id=&quot;1-최초-마이그레이션이-있는-장고-앱-찾기&quot;&gt;1. 최초 마이그레이션이 있는 장고 앱 찾기&lt;/h1&gt;
&lt;p&gt;이미 유저 모델을 만들 수 있는 장고 앱이 있다면 축하합니다! 바로 &lt;strong&gt;6번&lt;/strong&gt;으로 넘어가세요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;중요&lt;/strong&gt;: 위의 장고 앱은 꼭 최초 마이그레이션(예를 들어 &lt;code&gt;migrations/&lt;/code&gt; 디렉터리에 있는 &lt;code&gt;0001_initial.py&lt;/code&gt;)이 있어야 합니다. 장고 앱에 더 이상 모델이 없어도 중요하진 않습니다.&lt;/p&gt;
&lt;p&gt;만약 없으시다면 장고 앱을 새로 만들어보겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;2-새로운-장고-앱-만들기&quot;&gt;2. 새로운 장고 앱 만들기&lt;/h1&gt;
&lt;p&gt;다음 명령으로 프로젝트에 새로운 장고 앱을 생성합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;python3&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; manage.py&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; startapp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; accounts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;앱 이름은 &lt;code&gt;users&lt;/code&gt;처럼 아무렇게나 하셔도 좋습니다. 전 이미 &lt;code&gt;accounts&lt;/code&gt;의 이름을 가진 앱이 프로젝트에 있어, 이 튜토리얼에선 &lt;code&gt;accounts&lt;/code&gt; 앱을 사용하겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;3-최초-마이그레이션을-위해-가짜-모델-생성&quot;&gt;3. 최초 마이그레이션을 위해 가짜 모델 생성&lt;/h1&gt;
&lt;p&gt;새로운 앱의 &lt;code&gt;models.py&lt;/code&gt;를 편집하면서 다음 가짜 모델을 생성합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.db &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; models&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FakeModel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;models&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Model&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    pass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;나중에 마이그레이션이 모두 끝난 후 이 모델을 제거합니다.&lt;/p&gt;
&lt;h1 id=&quot;4-최초-마이그레이션-생성&quot;&gt;4. 최초 마이그레이션 생성&lt;/h1&gt;
&lt;p&gt;다음을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;python3&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; manage.py&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; makemigrations&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 앱에 최초 마이그레이션이 생깁니다. 예를 들어, 제 마이그레이션은 &lt;code&gt;accounts/migrations/0001_initial.py&lt;/code&gt;에 다음과 같이 생성되어 있었습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Generated by Django 3.2.5 on 2021-07-12 16:28&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.db &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; migrations, models&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Migration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;migrations&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Migration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    initial &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dependencies &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    operations &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        migrations.CreateModel(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;FakeModel&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            fields&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BigAutoField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;auto_created&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;primary_key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;serialize&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ID&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;5-중간-단계-릴리즈-배포하기&quot;&gt;5. 중간 단계 릴리즈 배포하기&lt;/h1&gt;
&lt;p&gt;이제 새로운 릴리즈를 만든 다음, &lt;strong&gt;모든 서버 관리자가 이 버전으로 업그레이드하게 한 후, 모든 서버에서 마이그레이션을 적어도 한 번 실행합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이렇게 하는 이유는 장고에서 무슨 마이그레이션을 적용했는지 기록해두기 때문입니다. 만약 새로운 마이그레이션에서 곧바로 커스텀 유저 모델을 위한 새로운 데이터베이스 테이블을 생성할 경우, 장고는 마이그레이션을 적용하는 도중 이미 테이블이 존재하는 것을 확인하고 오류를 발생시킵니다.&lt;/p&gt;
&lt;p&gt;이를 방지하기 위해서, 이전에 있던 모든 서버 인스턴스를 이 중간 단계 릴리즈로 업그레이드/마이그레이션한 다음, 유저 모델 마이그레이션 정보를 최초 마이그레이션에 추가로 패치해버리면, 이전 서버 인스턴스는 최초 마이그레이션을 두 번 적용하지 않을 것이고, 새로운 서버 인스턴스들은 유저 테이블이 없기에 최초 마이그레이션을 적용하게 됩니다.&lt;/p&gt;
&lt;h1 id=&quot;6-커스텀-유저-모델-생성&quot;&gt;6. 커스텀 유저 모델 생성&lt;/h1&gt;
&lt;p&gt;만약 &lt;strong&gt;1번&lt;/strong&gt;에서 바로 오셨다면 사용할 앱 이름을 대체하는 것을 꼭 잊지 마세요. 전 &lt;code&gt;accounts&lt;/code&gt;라는 앱 이름을 사용했습니다.&lt;/p&gt;
&lt;p&gt;이제 커스텀 유저 모델을 생성할 차례입니다. &lt;code&gt;models.py&lt;/code&gt;를 편집한 다음, 다음 코드를 추가합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.auth.models &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; AbstractUser&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AbstractUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Meta&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        db_table &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;auth_user&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기에서 &lt;code&gt;db_table&lt;/code&gt; 필드가 중요한데, 이전에 있던 유저 모델 테이블을 원할하게 그대로 끌어다 사용할 것이기 때문입니다. 또, 커스텀 유저 모델을 &lt;code&gt;User&lt;/code&gt;로 이름짓어 데이터베이스 내부의 관계도를 보존시키는 것도 중요합니다. 물론, 나중에 모델 이름을 변경하는 것도 가능하기에, 일단은 이렇게 추가해두세요.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1-5번&lt;/strong&gt;에 나와 있는 과정을 따라 하셨다면, &lt;code&gt;models.py&lt;/code&gt; 내용은 다음과 같을 겁니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.auth.models &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; AbstractUser&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.db &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; models&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AbstractUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Meta&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        db_table &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;auth_user&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FakeModel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;models&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Model&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    pass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 &lt;strong&gt;1번&lt;/strong&gt;에서 바로 오셨다면 &lt;code&gt;FakeModel&lt;/code&gt; 모델이 없어도 걱정하지 마세요.&lt;/p&gt;
&lt;h1 id=&quot;7-최초-마이그레이션을-수동으로-패치하기&quot;&gt;7. 최초 마이그레이션을 수동으로 패치하기&lt;/h1&gt;
&lt;p&gt;이제 앱의 최초 마이그레이션을 직접 수정해줍니다. 이 마이그레이션 파일에 예전 모델들을 위한 마이그레이션들도 포함되어 있을 겁니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;경고&lt;/strong&gt;: 이 패치는 2021년 7월 13일 장고 버전 3.2.5에서 작동하는 것을 확인했습니다. 이후 버전은 필드나 마이그레이션에 차이가 있을 수 있습니다. 만약 차이가 있는지 확인하려면 빈 장고 프로젝트를 만든 후, 커스텀 유저 모델을 생성한 다음, 마이그레이션을 생성해서 이 섹션에 나와 있는 패치와 비교해주세요. 만약 업데이트된 마이그레이션이 있다면, 그 빈 프로젝트에서 값을 복사하시면 됩니다!&lt;/p&gt;
&lt;p&gt;일단 필요한 의존성을 불러옵니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.auth.models&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.auth.validators&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.utils.timezone&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dependencies&lt;/code&gt; 섹션에 다음을 추가해 줍니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;0012_alter_user_first_name_max_length&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;업데이트 된 &lt;code&gt;dependencies&lt;/code&gt; 섹션:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dependencies &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;0012_alter_user_first_name_max_length&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;operations&lt;/code&gt; 섹션 안에 다음을 추가합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        migrations.CreateModel(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;User&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            fields&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BigAutoField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;auto_created&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;primary_key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;serialize&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ID&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;password&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;128&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;password&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last_login&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.DateTimeField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last login&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;is_superuser&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BooleanField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                     help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Designates that this user has all permissions without explicitly assigning them.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                     verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;superuser status&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;error_messages&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;unique&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;A user with that username already exists.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;150&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;unique&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              validators&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[django.contrib.auth.validators.UnicodeUsernameValidator()],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;first_name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;150&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;first name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last_name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;150&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;email&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.EmailField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;254&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;email address&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;is_staff&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BooleanField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                 help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Designates whether the user can log into this admin site.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                 verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;staff status&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;is_active&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BooleanField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Designates whether this user should be treated as active. Unselect this instead of deleting accounts.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;active&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;date_joined&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.DateTimeField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;django.utils.timezone.now, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;date joined&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;groups&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.ManyToManyField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;The groups this user belongs to. A user will get all permissions granted to each of their groups.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  related_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_set&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;related_query_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth.Group&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;groups&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_permissions&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.ManyToManyField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Specific permissions for this user.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                            related_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_set&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;related_query_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                            to&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth.Permission&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user permissions&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                &apos;db_table&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth_user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            managers&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;objects&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, django.contrib.auth.models.UserManager()),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ),&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 &lt;strong&gt;1-5번&lt;/strong&gt;을 똑같이 하셨다면 마이그레이션 파일이 다음과 같을 겁니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Generated by Django 3.2.5 on 2021-07-12 16:28&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.auth.models&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.auth.validators&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.utils.timezone&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.db &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; migrations, models&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Migration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;migrations&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Migration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    initial &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dependencies &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;0012_alter_user_first_name_max_length&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    operations &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        migrations.CreateModel(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;FakeModel&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            fields&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BigAutoField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;auto_created&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;primary_key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;serialize&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ID&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        migrations.CreateModel(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;User&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            fields&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BigAutoField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;auto_created&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;primary_key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;serialize&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ID&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;password&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;128&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;password&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last_login&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.DateTimeField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last login&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;is_superuser&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BooleanField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                     help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Designates that this user has all permissions without explicitly assigning them.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                     verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;superuser status&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;error_messages&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;unique&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;A user with that username already exists.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;150&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;unique&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              validators&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[django.contrib.auth.validators.UnicodeUsernameValidator()],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;first_name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;150&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;first name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last_name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;150&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;email&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.EmailField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;254&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;email address&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;is_staff&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BooleanField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                 help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Designates whether the user can log into this admin site.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                 verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;staff status&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;is_active&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BooleanField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Designates whether this user should be treated as active. Unselect this instead of deleting accounts.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;active&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;date_joined&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.DateTimeField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;django.utils.timezone.now, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;date joined&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;groups&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.ManyToManyField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;The groups this user belongs to. A user will get all permissions granted to each of their groups.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  related_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_set&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;related_query_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth.Group&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;groups&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_permissions&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.ManyToManyField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Specific permissions for this user.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                            related_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_set&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;related_query_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                            to&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth.Permission&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user permissions&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                &apos;db_table&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth_user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            managers&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;objects&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, django.contrib.auth.models.UserManager()),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;8-설정에서-커스텀-유저-모델-활성화하기&quot;&gt;8. 설정에서 커스텀 유저 모델 활성화하기&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;settings.py&lt;/code&gt; 파일에 다음 설정값을 추가합니다;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;AUTH_USER_MODEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;accounts.User&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때 &lt;code&gt;accounts&lt;/code&gt;를 사용하는 앱 이름으로 변경하는 것을 잊지 마세요!&lt;/p&gt;
&lt;h1 id=&quot;9-빈-마이그레이션-생성하기&quot;&gt;9. 빈 마이그레이션 생성하기&lt;/h1&gt;
&lt;p&gt;이제 커스텀 유저 모델로 전환하기 전에 마지막 마이그레이션 하나를 만들어야 합니다. 다음을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;python3&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; manage.py&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; makemigrations&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --empty&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; accounts&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; change_user_type&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때 &lt;code&gt;accounts&lt;/code&gt;를 사용하는 앱 이름으로 변경하는 것을 잊지 마세요. 장고는 새로운 빈 마이그레이션 파일을 생성하는데, 제 프로젝트에선 &lt;code&gt;accounts/migrations/0002_change_user_type.py&lt;/code&gt;에 파일이 생겼습니다&lt;/p&gt;
&lt;h1 id=&quot;10-빈-마이그레이션-수정하기&quot;&gt;10. 빈 마이그레이션 수정하기&lt;/h1&gt;
&lt;p&gt;다음 함수를 마이그레이션 파일 최상단에 추가합니다 (의존성 불러오기 아래에):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; change_user_type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(apps, schema_editor):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ContentType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; apps.get_model(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;contenttypes&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ContentType&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ct &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ContentType.objects.filter(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        app_label&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        model&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ).first()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ct:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ct.app_label &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;user&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ct.save()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그 다음, &lt;code&gt;operations&lt;/code&gt; 섹션에 다음을 추가합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      migrations.RunPython(change_user_type),&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;참고로 앱에 이전 마이그레이션이 있었을 경우 &lt;code&gt;dependencies&lt;/code&gt; 섹션이 다를 수 있습니다. 완료되면 마이그레이션 파일이 다음과 같아야 합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Generated by Django 3.2.5 on 2021-07-12 16:38&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.db &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; migrations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; change_user_type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(apps, schema_editor):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ContentType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; apps.get_model(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;contenttypes&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ContentType&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ct &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ContentType.objects.filter(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        app_label&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        model&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ).first()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ct:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ct.app_label &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;user&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ct.save()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Migration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;migrations&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Migration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dependencies &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;accounts&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;0001_initial&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    operations &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        migrations.RunPython(change_user_type),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;11-새로운-db_table-필드로-변경&quot;&gt;11. 새로운 &lt;code&gt;db_table&lt;/code&gt; 필드로 변경&lt;/h1&gt;
&lt;p&gt;이제 마이그레이션을 추가해서 기본 테이블로 이동해도 됩니다. &lt;code&gt;models.py&lt;/code&gt;를 수정한 다음, &lt;code&gt;Meta&lt;/code&gt; 클래스를 제거합니다. 이 변경으로 유저 모델이 비어 있다면, &lt;code&gt;pass&lt;/code&gt;를 추가하는 것도 잊지 마세요. 그럼 유저 모델이 다음과 같아야 합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AbstractUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    pass&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 마이그레이션을 새로 생성합니다.&lt;/p&gt;
&lt;p&gt;만약 &lt;strong&gt;1번&lt;/strong&gt;에서 &lt;strong&gt;6번&lt;/strong&gt;으로 바로 넘어오셨다면 여기에서 마치셔도 됩니다. 새로운 릴리즈를 생성한 다음, 관리자들이 업그레이드하도록 하시면 됩니다. 추후의 마이그레이션은 이전처럼 생성하고 적용하면 됩니다.&lt;/p&gt;
&lt;h1 id=&quot;12-임시-가짜-모델-삭제하기&quot;&gt;12. 임시 가짜 모델 삭제하기&lt;/h1&gt;
&lt;p&gt;만약 &lt;strong&gt;1-5번&lt;/strong&gt;을 따르셨다면, 최초 마이그레이션을 생성하기 위해 만들었던 가짜 모델을 제거해야 됩니다. &lt;code&gt;models.py&lt;/code&gt;를 수정하면서 &lt;code&gt;FakeModel&lt;/code&gt; 모델을 제거해주세요.&lt;/p&gt;
&lt;p&gt;새로운 마이그레이션을 생성한 다음 배포하시면 됩니다. 서버 관리자들이 중간 단계 릴리즈로 먼저 업그레이드 후 데이터베이스 마이그레이션을 진행하는 것을 잊지 않게 안내하시는 것을 추천합니다.&lt;/p&gt;
&lt;h1 id=&quot;결론&quot;&gt;결론&lt;/h1&gt;
&lt;p&gt;이 과정을 백엔드에서 PostgreSQL과 배포 관리용 도커 (Docker)를 사용하는 제 장고 프로젝트에서 시험했는데, 마이그레이션은 별 문제 없이 진행되었고 이상한 변경사항 없이 기존의 데이터베이스 테이블을 보존할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;위의 튜토리얼이 잘 작동했는지 안 했는지 꼭 밑에 댓글로 남겨주세요!&lt;/p&gt;
&lt;h1 id=&quot;참조&quot;&gt;참조&lt;/h1&gt;
&lt;p&gt;이 블로그 글은 &lt;a href=&quot;https://www.caktusgroup.com/blog/2019/04/26/how-switch-custom-django-user-model-mid-project/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Caktus Group이 작성한 블로그 글&lt;/a&gt;과 &lt;a href=&quot;https://code.djangoproject.com/ticket/25313&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;장고의 버그트래커에 올라와 있는 버그 리포트&lt;/a&gt; (특히 &lt;a href=&quot;https://code.djangoproject.com/ticket/25313#comment:24&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;댓글 24번&lt;/a&gt;)을 참조하여 작성되었습니다.&lt;/p&gt;</content:encoded></item><item><title>마인크래프트 LAN 서버 표시하기</title><link>https://ericswpark.com/ko/blog/2021/2021-06-25-minecraft-lan-advertising/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2021/2021-06-25-minecraft-lan-advertising/</guid><pubDate>Fri, 25 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;마인크래프트 멀티플레이 서버 목록에서 로컬 서버를 표시하는 방법을 찾다가 &lt;a href=&quot;https://void7.net/advertising-linux-minecraft-servers-to-the-lan/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;7년 전에 적힌 블로그 글을 읽게 되었습니다&lt;/a&gt;. 첨부된 파이썬 스크립트에 파이썬 3과 호환이 되도록 수정을 해주니 잘 동작합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;servers &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;motd1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;25565&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;motd2&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;25566&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;BROADCAST_IP&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;255.255.255.255&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;BROADCAST_PORT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 4445&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sock &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.socket(socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;AF_INET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SOCK_DGRAM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sock.setsockopt(socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SOL_SOCKET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SO_BROADCAST&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;LAN에 마인크래프트 서버 표시중&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; server &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; servers:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                msg &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;[MOTD]&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;[/MOTD][AD]&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%d&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;[/AD]&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; %&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (server[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;], server[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                sock.sendto(msg.encode(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;UTF-8&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;BROADCAST_IP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;BROADCAST_PORT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        time.sleep(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;파일 최상단에 있는 서버 배열만 수정하면 됩니다. &lt;code&gt;broadcast_to_lan.py&lt;/code&gt;과 같은 파일 이름으로 저장하고, &lt;code&gt;python3 broadcast_to_lan.py&lt;/code&gt;로 실행합니다.&lt;/p&gt;
&lt;p&gt;원글/스크립트 작성자이신 kebian께 감사합니다!&lt;/p&gt;</content:encoded></item><item><title>아이패드 스마트 키보드를 반품한 이유</title><link>https://ericswpark.com/ko/blog/2021/2021-06-24-why-i-returned-the-ipad-smart-keyboard/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2021/2021-06-24-why-i-returned-the-ipad-smart-keyboard/</guid><pubDate>Thu, 24 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;아이패드 스마트 키보드를 주문해 5분 정도 사용한 다음 바로 반품해버린 이유들을 설명해드리겠습니다.&lt;/p&gt;
&lt;p&gt;(참고로 이 글의 스마트 키보드는 아이패드 에어 3용 키보드입니다.)&lt;/p&gt;
&lt;h1 id=&quot;품질&quot;&gt;품질&lt;/h1&gt;
&lt;p&gt;처음 상자를 열었을 때, 가짜 키보드를 받은 줄 알았습니다.&lt;/p&gt;
&lt;p&gt;스마트 키보드의 “케이스” 부분은 괜찮았습니다. 원래 아이패드에 씌워놓았던 스마트 커버와 질감이 똑같았고, 재질도 완전히 원래 케이스랑 일치했습니다.&lt;/p&gt;
&lt;p&gt;하지만, 스마트 키보드의 “키보드” 부분은 품질이 끔찍했습니다. 무슨 원본의 본을 따 만든 키보드인 줄 알았는데, 리뷰 사진들을 보고 나서 애플이 &lt;strong&gt;실제로&lt;/strong&gt; 만들은 것을 알게 됐습니다. 무슨 천 같은 재질이 키들을 감싸면서 먼지 유입을 방지한다고는 하는데, 느낌상 매우 싼 느낌이 납니다.&lt;/p&gt;
&lt;p&gt;진짜 너무한 것 같습니다. 사진은 못 찍었지만, 온라인에서 키보드 사진만 봐도 이상한 텍스쳐 때문에 싼 느낌이 확 납니다.&lt;/p&gt;
&lt;h1 id=&quot;연결성&quot;&gt;연결성&lt;/h1&gt;
&lt;p&gt;키보드 품질이 조금 안 좋아도, 블루투스 키보드보다 연결성이 괜찮으면 된다고 생각했습니다. 아이패드에 있는 연결 단자로 연결하는데, 설마 블루투스보다 품질이 떨어지지는 않겠죠?&lt;/p&gt;
&lt;p&gt;그 설마가 맞았습니다… 처음 키보드를 연결했을 땐 작동이 괜찮았습니다. 키보드 입력은 괜찮게 동작했고, 키를 눌렀을 때 반응도 정상적으로 확인되었습니다. 그런데, 아이패드 화면에 반사가 일어나 화면이 잘 안 보여서 아이패드를 살짝 앞으로 기울였더니 키보드 연결이 바로 끊어집니다.&lt;/p&gt;
&lt;p&gt;이상하게도, 아이패드의 밑 부분이 키보드의 고무 “바”에 닫지 않으면 키보드가 작동하질 않습니다. 단자로 연결하면 이런 문제가 없을 줄 알았는데, 왜 아이패드 화면 각도를 조정하려 밑 부분을 2센치 옮기면 연결이 끊기는 걸까요?&lt;/p&gt;
&lt;p&gt;진짜 연결 끊기는 것이 이상해서 불량품이 걸렸나 했습니다. 기울이는 것만으로 키보드 동작이 멈추는 것은 하자가 아니면 진짜 괴상한 문제인 것 같습니다.&lt;/p&gt;
&lt;h1 id=&quot;키감&quot;&gt;키감&lt;/h1&gt;
&lt;p&gt;키보드의 가장 중요한 부분은 역시 키감입니다. 키가 너무 스펀지같거나 초라한 (?) 느낌이 나면 안 되는데, 스마트 키보드는 키감이 나쁘진 않았지만 키 위의 천 재질이 키감을 떨어뜨리는 느낌을 받았습니다.&lt;/p&gt;
&lt;p&gt;스마트 키보드를 사용하면서 키를 실수로 못 눌렀다던가, 키가 이중으로 눌리는 현상을 많이 경험했습니다. 타자를 치면서 손이 키보드에 익숙하지 않아 실수가 잦은 것 같았습니다. 물론, 키보드를 5분 정도밖에 사용하지 않아서 아마도 시간이 지나면 키보드에 익숙해졌을 수도 있지만, 제 생각에는 다른 키보드가 훨씬 더 괜찮은 키감과 타자 편의성을 제공하는 것 같습니다.&lt;/p&gt;
&lt;p&gt;물론 스마트 키보드처럼 작은 공간에 좋은 품질의 키보드를 구겨넣는 것은 힘들다는 것을 알고 있습니다. 키보드 크기가 작으니 키감을 살리는 것이 힘든 것은 알고 있습니다. 하지만, 키보드의 주 목적은 타자를 위한 건데, 키감이 좋지 않아 타자가 불편하다면 주 목적을 달성하지 않아 제 생각으론 좋은 제품이 아닌 것 같습니다.&lt;/p&gt;
&lt;h1 id=&quot;빠진-키들&quot;&gt;빠진 키들&lt;/h1&gt;
&lt;p&gt;키를 얘기하면서 잠시 짚고 넘어가야 되는 부분이 있는데, 스마트 키보드에선 중요한 키 몇 개가 빠져 있었습니다.&lt;/p&gt;
&lt;p&gt;일단 제 생각으로 가장 중요한 Esc 키가 빠져있어, 개발 작업을 진행할 때 방해가 많이 되었습니다. Blink를 사용해서 서버로 SSH 접속을 해서 작업을 하는데, Esc 키가 없으면 키를 필요로 하는 많은 리눅스 프로그램이 작동하지 않습니다. 제 생각으론 제대로 된 키보드는 꼭 Esc 키가 있어야 될 것 같습니다.&lt;/p&gt;
&lt;p&gt;그리고 키보드 상에 function (기능) 키들이 하나도 없는데, 볼륨이나 밝기 조절, 홈 화면으로 돌아가기 버튼, 미디어 재생 조절 버튼들이 하나도 없습니다. 블루투스 키보드엔 거의 대부분 있는 기능인데, 있어야 아이패드 사용이 훨씬 편해집니다. 없으면 설정을 조정하려 아이패드 화면이나 옆에 볼륨 버튼으로 손이 가 귀찮습니다.&lt;/p&gt;
&lt;h1 id=&quot;이동성과-무게&quot;&gt;이동성과 무게&lt;/h1&gt;
&lt;p&gt;스마트 키보드의 가장 강력한 장점은 언제나 사용할 수 있게 아이패드에 부착해서 들고 다닐 수 있는 점입니다. 하지만, 아이패드에 언제나 부착되어 있다면 아이패드 자체가 무거워지고, 들고 다니기에 힘들어집니다.&lt;/p&gt;
&lt;p&gt;스마트 키보드는 자석으로 부착이 되서, 키보드가 필요없는 상황에서 (예를 들어, 넷플릭스 시청) 아이패드를 단독으로 사용하는 것이 힘들어집니다. 블루투스 키보드는 장착이 되어 있지 않아 그냥 아이패드를 들어 올리면 되는데, 스마트 키보드는 힘을 줘서 먼저 자석에서 분리해내야 아이패드를 단독으로 사용하거나, 키보드를 뒤로 접고 더 무거운 아이패드를 사용해야 되기 때문입니다.&lt;/p&gt;
&lt;p&gt;키보드를 구입하면서 제가 바라던 것은 적어도 로지텍 K380보다 무게가 적길 바랬지만, 두 제품을 비교한 결과 그렇게 큰 차이는 느끼지 못했습니다. 그리고 위에서도 언급했듯이 로지텍 키보드는 아이패드에 붙어있지 않아 사용하기에 무척 편리했습니다.&lt;/p&gt;
&lt;h1 id=&quot;가격&quot;&gt;가격&lt;/h1&gt;
&lt;p&gt;한국에서 스마트 키보드는 애플에서 현재 ₩199,000에 판매되고 있습니다. (물론 오픈마켓에선 가격이 조금 더 싸긴 합니다.) 비교 대상으로 K380은 ₩34,500, 더 싼 키보드는 약 ₩21,670에 판매되는데, 정신나간 수준입니다. 그 가격으론 블루투스 키보드를 여러 대 구입해 오랫동안 사용할 수 있고, 남은 돈으로 글을 잘 작성하게 커피나 한 잔 사 마실 수 있습니다.&lt;/p&gt;
&lt;h1 id=&quot;요약&quot;&gt;요약&lt;/h1&gt;
&lt;p&gt;위에 생각을 정리해보면 결론이 엄청 쉽습니다. 스마트 키보드는 &lt;strong&gt;이해할 수 없는&lt;/strong&gt; 제품입니다. 하나라도 성공했더라면 애플이 직접 만든 기기니까 납득을 어떻게든 하면서 구입할 수 있었을 수도 있지만, 하나도 충족을 못 하고 단점이 너무나 많아 시중의 다른 써드파티 키보드와 비교했을 때 구입 가치가 전혀 없는 제품입니다.&lt;/p&gt;</content:encoded></item><item><title>숨어있는 비디오 플레이어 속도 조절하기</title><link>https://ericswpark.com/ko/blog/2021/2021-06-22-speeding-up-invisible-video-players/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2021/2021-06-22-speeding-up-invisible-video-players/</guid><pubDate>Tue, 22 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;온라인에서 둥영상을 볼 때 플레이어가 기능을 지원하면 항상 속도를 2배속으로 하고 봅니다. 거의 습관이 되었는데, 둥영상을 2배속으로 해도 (거의) 대부분 이해가 가능하기에 시간을 절약할 수 있어서 그럽니다.&lt;/p&gt;
&lt;p&gt;어떤 비디오 플레이어는 무슨 이유에선지 속도 제어 기능을 사용자에게 노출하지 않는데, 그렇게 크게 문제되진 않습니다. 거의 대부분의 웹사이트는 HTML5로 둥영상을 노출하는데, 그럼 브라우저의 개발툴로 속도를 수동으로 조절하면 되기 때문입니다.&lt;/p&gt;
&lt;p&gt;그런데, 오늘 네이버 뉴스에서 둥영상을 보다가, 개발툴에서 속도를 조절하려고 보니 개발툴이 둥영상 태그를 못 찾았습니다. 왜 그런지 확인하러 갑시다!&lt;/p&gt;
&lt;h1 id=&quot;개발툴에서-어떻게-수동으로-비디오-속도를-조절하나요&quot;&gt;개발툴에서 어떻게 수동으로 비디오 속도를 조절하나요?&lt;/h1&gt;
&lt;p&gt;이 명령이나:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;document.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;video&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).defaultPlaybackRate &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2.0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;또는 이 명령으로 속도 조절이 가능합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;document.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getElementsByTagName&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;video&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].playbackRate &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(이때 두 번째 명령은 &lt;code&gt;[0]&lt;/code&gt;이 추가적으로 삽입되어 있는데, 이는 &lt;code&gt;getElementsByTagName()&lt;/code&gt;가 (노드가 한 개만 있어도) 배열을 돌려주기 때문입니다. 따라서 배열의 첫 번째 노드를 선택하기 위해 &lt;code&gt;[0]&lt;/code&gt;를 사용합니다.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;경험상으론 95%의 경우 잘 동작하는데, 네이버에선 노드가 정의되어 있지 않다면서 오류가 납니다. 그럼 네이버의 사이트에선 뭐가 특별할까요?&lt;/p&gt;
&lt;h1 id=&quot;테스트합시다&quot;&gt;테스트합시다!&lt;/h1&gt;
&lt;p&gt;일단 제가 오늘 실험하면서 사용할 &lt;a href=&quot;https://news.naver.com/main/read.nhn?oid=056&amp;#x26;aid=0011068640&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;뉴스 기사입니다.&lt;/a&gt; 위의 두 명령을 한번 사용해보세요. 노드 정의가 되지 않았다며 오류가 날 겁니다.&lt;/p&gt;
&lt;p&gt;재생 버튼을 누르면 둥영상 재생이 시작됩니다. 그럼 개발툴로 가서 비디오 태그를 찾겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;no-video-tag&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1942&quot; height=&quot;202&quot; src=&quot;/_astro/no-video-tag.Dto8DGRH_Zj29k5.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이상하게도 비디오 태그가 없네요! 선택 툴로 찾으려 해도 둥영상 위의 &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt; 노드가 선택을 방해합니다. &gt;:(&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;overlay-div&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2022&quot; height=&quot;1304&quot; src=&quot;/_astro/overlay-div.DiZYD7lL_Z1kpmWs.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;그럼 수동으로 찾겠습니다. 살짝 내려가서 수동으로 일일히 열어보고 검색하니 태그가 &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt; 노드 사이에서 나옵니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;video-tag-found&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2072&quot; height=&quot;1472&quot; src=&quot;/_astro/video-tag-found.DXHSqYH__1wxnzo.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;비디오 태그는 있는데, 검색 도구가 찾질 못합니다??&lt;/p&gt;
&lt;p&gt;그럼 이 페이지 전체를 HTML 파일로 저장한 다음, 텍스트 편집기로 검색해서 태그를 찾을 수 있을지 확인해 보겠습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;text-editor-fail&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1418&quot; height=&quot;546&quot; src=&quot;/_astro/text-editor-fail.5usy13Yl_Z10uJg.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;(한글 글씨가 깨져 보이는 것은 무시하셔도 됩니다. 이는 EUC-KR 인코딩 대신 UTF-8 인코딩으로 파일이 열렸기 때문에 그러며, HTML 태그는 괜찮기에 진행해도 괜찮습니다.)&lt;/p&gt;
&lt;p&gt;그래도 찾을 수 없기에 여기에서 몇 가지를 짚고 넘어가겠습니다. 일단, 페이지에 둥영상 플레이어가 없는 걸 봐선 자바스크립트로 동적으로 로드된다는 것을 확인할 수 있습니다. 제 생각으론 아마도 브라우저가 페이지가 로드된 후 DOM에 추가된 노드들은 찾지 못하는 것 같습니다.&lt;/p&gt;
&lt;p&gt;그런데, 콘솔을 어쩌피 페이지 로드가 끝난 후 사용되기에 콘솔 내부에선 노드를 사용할 수 있지 않을까요?&lt;/p&gt;
&lt;h1 id=&quot;safari-사용하기&quot;&gt;Safari 사용하기&lt;/h1&gt;
&lt;p&gt;Firefox를 기본 브라우저로 사용하는데, 만약 Firefox의 버그일 수도 있지 않을까요?&lt;/p&gt;
&lt;p&gt;그럼 Safari로 한번 해 보겠습니다. 페이지를 로드한 후, 개발툴에서 Ctrl-F로 검색을 하니:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;safari-finds&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1328&quot; height=&quot;396&quot; src=&quot;/_astro/safari-finds.NNPOvAk1_Z2uC5EN.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Safari는 제대로 노드를 인식하네요! 그럼 명령이 잘 작동하는지 확인하겠습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;safari-commands&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;962&quot; height=&quot;270&quot; src=&quot;/_astro/safari-commands.DWhMxZWM_Z1bOsb3.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;일단 첫 번째 명령은 오류 없이 실행되었지만, 둥영상 플레이어는 그래도 일반 속도로 재생이 되었습니다. 두 번째 명령을 실행하니 둥영상이 2배속으로 재생되면서 정상 작동하는 것을 확인했습니다!&lt;/p&gt;
&lt;p&gt;그럼 Firefox에서 버그를 발견한 걸까요?&lt;/p&gt;
&lt;h1 id=&quot;컨텍스트-바꾸기&quot;&gt;컨텍스트 바꾸기&lt;/h1&gt;
&lt;p&gt;Firefox 콘솔로 돌아가 보겠습니다. 놓친 게 뭐가 있을까요? 자바스크립트 디버깅 콘솔의 “컨텍스트 설정”을 확인하지 못했습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;firefox-context-button&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3358&quot; height=&quot;1052&quot; src=&quot;/_astro/firefox-context-button.iunLsQKh_ZREFku.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;설명하자면 웹사이트에선 프레임이 여러 개 있을 경우, Firefox에서 자동으로 첫 번째 프레임 속에서 자바스크립트 명령을 실행합니다. 하지만 둥영상 플레이어 노드는 다른 컨텍스트/프레임 속에 있어서 자바스크립트 콘솔이 인식을 하지 못하는 것이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;firefox-second-context&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3358&quot; height=&quot;1052&quot; src=&quot;/_astro/firefox-second-context.BYpHXhpi_2bp2on.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;정확한 컨텍스트를 고른 다음에야 명령이 정상 동작하여 둥영상 플레이어가 2배속으로 재생되는 것을 확인했습니다. ^^&lt;/p&gt;
&lt;h1 id=&quot;요약하자면&quot;&gt;요약하자면?&lt;/h1&gt;
&lt;p&gt;오늘 배운 것은 개발툴의 자바스크립트 콘솔이 다른 컨텍스트에서 실행될 수 있다는 것을 확인했고, 또 어쩔땐 노드가 특정 컨텍스트 내부에서만 사용이 가능하다는 것을 배웠습니다.&lt;/p&gt;
&lt;p&gt;Firefox 개발자들이 고칠 것이 있다면, &lt;code&gt;getElement*()&lt;/code&gt; 함수가 현재 컨텍스트 내부에서 노드를 찾지 못한다면 다른 컨텍스트에 노드가 있는지를 확인한 후 개발자에게 다른 컨텍스트로 전환할지 물어보는 것이 좋다고 생각합니다. 또, 컨텍스트 버튼이 드롭다운 메뉴로 변경되어 현재 컨텍스트를 보여주는 게, 더 직관적으로 무슨 컨텍스트 내부에 있는지 확인할 수 있게 할 것 같습니다.&lt;/p&gt;
&lt;p&gt;또, Firefox에선 페이지가 완전히 로드된 다음 스크립트에 의해 추가된 HTML 노드들을 찾지 못하는데, Safari에선 정상적으로 노드가 검색이 되는 것을 보면 아마도 Firefox 버그가 아닐까 추측해봅니다.&lt;/p&gt;
&lt;p&gt;(아, 그리고 네이버는 둥영상 플레이어에 속도 제어 기능 좀 넣어주었으면…!)&lt;/p&gt;</content:encoded></item><item><title>macOS에서 C 포인터 이중-free 버그 고치기</title><link>https://ericswpark.com/ko/blog/2021/2021-04-13-c-pointer-double-free-fix-on-macos/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2021/2021-04-13-c-pointer-double-free-fix-on-macos/</guid><pubDate>Tue, 13 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;C 프로그램을 짜는데 계속 다음과 같은 에러가 발생합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;main(44466,0x10ae7ce00) malloc: *** error for object 0x7000000000000000: pointer being freed was not allocated&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;main(44466,0x10ae7ce00) malloc: *** set a breakpoint in malloc_error_break to debug&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;zsh: abort      ./main test_cases/test_1.html output.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이상하게도 &lt;code&gt;lldb&lt;/code&gt;를 쓰면 에러가 안 나는데, 여러 번 돌려보니 그제서야 &lt;code&gt;lldb&lt;/code&gt;가 &lt;code&gt;malloc_error_break&lt;/code&gt; breakpoint에서 멈춰버립니다. 메모리 버그가 이래서 잡기 힘들고 짜증납니다..&lt;/p&gt;
&lt;p&gt;근데, &lt;code&gt;lldb&lt;/code&gt;가 디버거로서 줘야 할 스택 trace 정보를 하나도 안 주네요. 리눅스 &lt;code&gt;gdb&lt;/code&gt;에서도 한 번 프로그램을 돌려봤는데, 리눅스에서도 잘 크래시하지 않아 디버깅하는데 시간을 좀 많이 썼습니다. 리눅스에서 크래시하게 여러 번 프로그램을 돌렸는데, 크래시했을 때 주는 스택 trace도 하나도 유용하지 않고, &lt;code&gt;valgrind&lt;/code&gt;로 돌렸을 때도 별다른 정보를 제공하지는 않았습니다.&lt;/p&gt;
&lt;p&gt;운 좋게도, 맥에서 &lt;code&gt;clang&lt;/code&gt;(LLVM)이란 컴파일러를 사용하면 &lt;a href=&quot;https://clang.llvm.org/docs/AddressSanitizer.html&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;AddressSanitizer&lt;/a&gt;라는 모듈이 같이 오는데, 이 모듈을 가지고 프로그램의 무슨 부분이 크래시하는지 확인할 수 있습니다. 그래서 AddressSanitizer를 활성화한 &lt;code&gt;clang&lt;/code&gt;을 가지고 프로그램을 다시 컴파일해봤는데:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;clang&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -g&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -fsanitize=address&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; main_debug&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; main.c&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (... &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;rest&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; source&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; files&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;프로그램을 실행시키니, AddressSanitizer가 바로 무슨 부분이 문제인지 알려줍니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ericswpark@Erics-MacBook-Pro Project % ./main_debug test_cases/test_1.html debug_output.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;AddressSanitizer:DEADLYSIGNAL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;=================================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;==44732==ERROR: AddressSanitizer: SEGV on unknown address (pc 0x000102e04b98 bp 0x7ffeece1ba20 sp 0x7ffeece1b9f0 T0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;==44732==The signal is caused by a READ memory access.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;==44732==Hint: this fault was caused by a dereference of a high value address (see register values below).  Dissassemble the provided pc to learn which register was used.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #0 0x102e04b98 in __asan::Allocator::Deallocate(void*, unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType)+0x48 (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x6b98)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #1 0x102e4732a in wrap_free+0x10a (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x4932a)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #2 0x102de8131 in html_tag_free main.c:278&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #3 0x102de7f7c in stack_pop main.c:298&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #4 0x102de79e5 in main main.c:219&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #5 0x7fff203b9620 in start+0x0 (libdyld.dylib:x86_64+0x15620)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;==44732==Register values:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;rax = 0x0000000000000002  rbx = 0xbebebebebebebebe  rcx = 0x0000000000000003  rdx = 0x0000000000000000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;rdi = 0xbebebebebebebebe  rsi = 0xbebebebebebebebe  rbp = 0x00007ffeece1ba20  rsp = 0x00007ffeece1b9f0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; r8 = 0x00007ffeece1ba30   r9 = 0x0000000000000001  r10 = 0xffffffffffffffff  r11 = 0x00000fffffffffff&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;r12 = 0x0000000000000001  r13 = 0x0000000000000000  r14 = 0x00007ffeece1ba30  r15 = 0x0000000102ea2d40&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;AddressSanitizer can not provide additional info.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SUMMARY: AddressSanitizer: SEGV (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x6b98) in __asan::Allocator::Deallocate(void*, unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType)+0x48&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;==44732==ABORTING&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;zsh: abort      ./main_debug test_cases/test_1.html debug_output.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;main.c&lt;/code&gt; 파일의 278번째 줄에서 문제가 발생하고 있네요. 여기에서 문제는 struct 변수를 초기화할 때 한 character 배열을 &lt;code&gt;NULL&lt;/code&gt;로 설정하지 않아서 발생하는 문제였습니다. 다음 코드 블럭에서:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (var&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;chararr)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    free&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(var-&gt;chararr);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;     // 문제!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    var-&gt;chararr &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;var-&gt;chararr&lt;/code&gt;이 &lt;code&gt;NULL&lt;/code&gt;인지 확인하는데, 아까 초기화할 때 &lt;code&gt;NULL&lt;/code&gt;로 설정하지 않았으니 그 포인터에 담겨있던 메모리 주소를 마구잡이로 그냥 &lt;code&gt;free&lt;/code&gt;해버려서 문제가 발생한 것입니다.&lt;/p&gt;
&lt;p&gt;다음부터 프로그램에 이런 메모리 버그가 발생하면 &lt;code&gt;clang&lt;/code&gt;이랑 같이 오는 AddressSanitizer 모듈로 프로그램의 무슨 부분이 문제인지 확인하세요!&lt;/p&gt;</content:encoded></item><item><title>구글 포토와 아이클라우드 포토</title><link>https://ericswpark.com/ko/blog/2021/2021-03-07-google-photos-and-icloud-photos/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2021/2021-03-07-google-photos-and-icloud-photos/</guid><pubDate>Sun, 07 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;구글/아이클라우드 포토를 쓰면서 사용기 및 문제점을 적어봅니다.&lt;/p&gt;
&lt;h1 id=&quot;아이클라우드-포토에서-구글-포토로-갈아타기&quot;&gt;아이클라우드 포토에서 구글 포토로 갈아타기&lt;/h1&gt;
&lt;p&gt;애플 기기가 아직 있다면 과정이 엄청 쉽습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;아이클라우드 포토를 끕니다. 모달이 나오면 사진과 둥영상을 모두 다운받는 옵션을 선택합니다. 이 옵션을 선택하지 않으면 구글 포토에 올라가는 사진의 품질이 “최적화”된 나쁜 품질로 업로드됩니다.&lt;/li&gt;
&lt;li&gt;구글 포토를 다운받습니다.&lt;/li&gt;
&lt;li&gt;구글 포토 백업을 키고 백업이 완료될때까지 기다립니다.&lt;/li&gt;
&lt;li&gt;이동이 완료되면 구글 포토를 삭제하고 필요하다면 아이클라우드 포토에서 사진/둥영상을 삭제합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;반대로-갈아타기&quot;&gt;반대로 갈아타기&lt;/h1&gt;
&lt;p&gt;아이러니하게도, 다시 애플 생태계에 들어오는 것이 더 어렵습니다.&lt;/p&gt;
&lt;h1 id=&quot;구글-테이크아웃은-쓰지-마세요&quot;&gt;구글 테이크아웃은 쓰지 마세요&lt;/h1&gt;
&lt;p&gt;첫번째 시도로 구글 테이크아웃(Google Takeout)을 사용해서 구글 포토에서 아이클라우드로 사진과 둥영상을 옮기려 시도했습니다. 여기서 문제가 발생했는데, 구글 테이크아웃이 내보내준 사진과 둥영상의 메타데이터가 다 파기되어 나옵니다. (여기에서 메타데이터는 사진을 찍을때 삽입되는 추가 정보로, 주로 사진을 찍은 시간과 위치 정보가 포함되어 있습니다.)&lt;/p&gt;
&lt;p&gt;사실 완전히 메타데이터를 파기하는 것은 아니고, 파일을 쪼개서 &lt;code&gt;.json&lt;/code&gt; 형식으로 메타데이터를 출력합니다. 이렇게 “쪼개진” 파일은 원본 매타데이터가 손상되어 있는 상태로 나와서, 날짜 정보가 테이크아웃을 한 날짜로 설정됩니다. 짜증나게도, 모든 사진에 이렇게 적용되는 것도 아니라 왜 구글이 이러는지 의아합니다. 어떤 사진과 둥영상은 원본 메타데이터가 고스란히 박혀 나오는데, 또 중복되서 &lt;code&gt;.json&lt;/code&gt;이 다시 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;아마도 다시 메타데이터를 원본 파일에 박아넣게 스크립트를 작성하면 되겠죠.&lt;/del&gt; 그럴 필요는 없는게, 아래에 이미 다른 분이 작성한 스크립트를 링크해두었습니다. 그런데 이런 걸 아예 모르는 분들에겐 완전히 끔찍한 경험일텐데 왜 굳이 구글이 이렇게 하는지 이해가 되질 않습니다.&lt;/p&gt;
&lt;p&gt;거기에다가 이 방식으로 사진을 추출하면 특정 “라이브 포토”(애플의 움직이는 사진 기능)는 보존되지 않습니다. 물론 이건 구글의 탓이라기보단 애플한테 책임이 있을 가능성이 큽니다. 쉽게 말해서, 만약 사진이 JPG/PNG 포맷이고 “라이브 포토”가 MP4면 맥의 포토 어플에 끌어놓으면 다시 깔끔하게 “라이브 포토”로 병합됩니다. &lt;strong&gt;하지만,&lt;/strong&gt; 사진이 HEIC/HEIF(고효율 포맷)이라면, 맥에서 인식을 하지 못하고 두 사진/둥영상으로 쪼개놓아 불러오게 됩니다.&lt;/p&gt;
&lt;p&gt;그래서 이런걸 아예 모르고 그냥 테이크아웃이 준 사진/둥영상을 아이클라우드로 불러오게 된다면 사진 라이브러리가 망가집니다. 한번 이렇게 불러오기를 진행했더니 메타데이터 시간이 안 맞아서 사진이 뒤죽박죽 섞이고, 아까의 HEIC/HEIF 버그로 한 사진이어야 될 것이 여러 장의 사진으로 나뉘어져서 라이브러리가 엉망이 됐습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;수정글&lt;/strong&gt;: &lt;a href=&quot;https://github.com/TheLastGimbus/GooglePhotosTakeoutHelper&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;테이크아웃에서 쪼개져버린 메타데이터를 병합해주는 파이썬 스크립트가 있긴 있는데&lt;/a&gt;, 이 스크립트도 HEIC/HEIF 파일은 지원하지 않기에, 구글 테이크아웃으로 사진을 내보내는 방법은 추천하지 않습니다.&lt;/p&gt;
&lt;h1 id=&quot;구글-포토에서-아이클라우드-포토로-갈아타기&quot;&gt;구글 포토에서 아이클라우드 포토로 갈아타기&lt;/h1&gt;
&lt;p&gt;그럼 제대로 사진과 둥영상을 옮기는 방법을 알아보겠습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;아이폰이나 아이패드에서 아이클라우드 포토를 킵니다.&lt;/li&gt;
&lt;li&gt;구글 포토 어플을 다운받습니다.&lt;/li&gt;
&lt;li&gt;구글 포토 어플을 실행했을때, 자동 백업을 키면 &lt;strong&gt;안 됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;사진 한 장을 누르면서 2초 기다렸다가 드래그해서 여러 장을 동시에 선택합니다. &lt;strong&gt;이때, 사진의 양이 너무 많으면 다운로드가 실패합니다!&lt;/strong&gt; 거기에다가, 파일 타입이 섞이면 (예를 들어 사진과 둥영상을 동시에 선택했다면) 다운로드가 멈춰버리는 문제가 있습니다. 구글 포토 어플이 참 끔찍합니다.&lt;/li&gt;
&lt;li&gt;내보내기할 파일을 선택했다면 “공유” 버튼을 누른 다음에 “기기에 저장” 버튼을 누릅니다. 웹 버전과는 다르게 점 3개 메뉴 아래에 다운로드 옵션이 있는 게 아니라 공유 메뉴에 옵션이 위치해 있습니다.&lt;/li&gt;
&lt;li&gt;구글 포토가 사진과 둥영상을 자동으로 다운받습니다.&lt;/li&gt;
&lt;li&gt;다운로드가 완료되면, 구글 포토 어플을 삭제합니다. &lt;strong&gt;이때 구글 포토 어플에서 사진을 지우면 안됩니다. 휴대폰 어플상에서 사진을 지우면 방금 다운받았던 사진도 같이 지워지고, 결과적으로 아이클라우드 포토에서도 지워지게 됩니다!&lt;/strong&gt; 만약 구글 포토에서 사진을 삭제하고 싶다면 어플을 먼저 삭제한 후, 웹 버전에서 사진을 지우시면 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;구글 테이크아웃과 달리, 이 방식은 메타데이터와 “라이브 포토”들을 모두 보존시킵니다.&lt;/p&gt;
&lt;p&gt;물론 아까 적었듯이 구글 포토 어플도 버그투성이입니다. 일단 다운로드와 내보내기 기능이 엄청 느리고 버그가 많습니다. 여러 번 다운로드를 진행하면 어쩌다가 한번씩 “다운로드에 실패했습니다” 문구가 뜨는데 (이 오류는 거의 대부분 사진 타입을 섞어서 발생합니다. 즉, 사진과 둥영상을 같이 다운받았다거나, 둥영상과 “라이브 포토”를 같이 받았다거나, 패노라마와 연속 촬영(버스트 샷, burst shot) 사진을 같이 받았다거나, 등등), 이 문구가 떴을 때 진짜로 다운로드가 실패했다면 그냥 다시 다운을 실행하면 되는데, 문제는 구글 포토가 다운로드가 완료된 몇몇 사진과 둥영상을 이중으로 다운받습니다. 그래서 다시 다운로드를 시작하면 몇 개의 사진은 중복이 됩니다.&lt;/p&gt;
&lt;p&gt;거기에다가 어플 상의 진행 표시줄은 완전히 쓸데없는데, 다운로드 도중에 좌우로 왔다갔다합니다. 거기에다가 멈춰버리거나, 그냥 에러를 내뱉고 사라지는 경우도 빈번합니다. 그냥 전체 선택 크기로 진행 표시줄을 만들면 되는데 왜 굳이 이렇게 코드를 짜놓았을까 이해가 되질 않습니다. 진행 표시줄이 일정한 속도로 움직이진 않아도, 적어도 반대로 가는 경우는 없어야 될 것 같은데…&lt;/p&gt;
&lt;p&gt;아까 적었듯이 다운로드는 느리고 종종 멈춰버립니다. 멈추면 수동으로 취소해야 하고, 취소한 다음 무슨 사진을 다운받았고 무슨 사진은 안 받았는지 일일히 확인한 다음, 다운된 사진들을 선택 해제시키고 다시 다운로드를 시작시켜야 합니다.&lt;/p&gt;
&lt;p&gt;또다른 버그로는 라이브러리에 저장된 연속 촬영 사진을 수동으로 다운받아야 됩니다. 여기에서 “수동”이 진짜 “수동” 그 자체인데, 연속 촬영 사진을 연 다음, 프레임 하나를 선택하고, 저장하고, 두 번째 프레임을 선택하고, 저장하고, 세 번째 프레임을 선택하고… 이 과정을 모든 연속 촬영 프레임에 반복해서 수행해야 합니다. 그래야지 연속 촬영 사진이 완전히 다운받아지는데, 과정 하나는 참 복잡하고 귀찮습니다!&lt;/p&gt;
&lt;p&gt;마지막 버그는 꽤 심각한데, &lt;strong&gt;구글 포토가 조용히 몇몇 HEIC/HEIF 사진을 손상시킵니다.&lt;/strong&gt; 그래서 아까 과정 중 하나가 구글 포토의 자동 백업 기능을 끄는 것입니다. 안 그러면, 구글 포토가 다운받던 도중 손상된 파일을 다시 업로드한다면 그 사진은 다시 복구할 수 없기 때문입니다. 근데 특이하게 이 버그에 걸려든 사진들이 모두 다 촬영후 편집을 한 사진들이라서, 아마도 구글 포토가 사진을 다시 다운받을 때 편집 정보를 제대로 불러오지 않아 생기는 문제인 것 같습니다. 만약 사진이 손상되면 아이폰 포토 어플 상에서는 빈 칸으로 표시되고, 사진을 열었을 경우 검은 화면이 나오면서 오른쪽 아래에 느낌표가 출력됩니다. 느낌표를 누르면 “이 사진의 고품질 버전을 로딩하는 도중 오류가 발생했습니다”가 표시되는데, 구글 포토에서 사진을 다시 다운받아도 문제가 해결되지는 않습니다.&lt;/p&gt;
&lt;p&gt;우회 방안이 있긴 있는데, 맥 포토 어플에서 사진을 우클릭한 다음 “원본으로 복귀”를 누르면 편집 데이터를 삭제하면서 사진을 복구시킵니다.&lt;/p&gt;
&lt;p&gt;진짜 구글 포토, 이런 건 너무 성의없이 만든 것 같아요.&lt;/p&gt;
&lt;h1 id=&quot;그럼-무엇이-더-낫냐&quot;&gt;그럼 무엇이 더 낫냐?!&lt;/h1&gt;
&lt;p&gt;앱 개발을 위해 iOS하고 안드로이드를 둘 다 쓰고 있어서, 두 서비스를 다 써봤는데, 구글 포토가 기능이 훨씬 더 많고 안드로이드에서도 지원되서 아이클라우드 포토를 이길 줄 알았더니… 오히려 구글 포토가 내보내기 과정에서 문제가 많이 드러나 둘 중 하나를 고르라고 하면 고민이 됩니다.&lt;/p&gt;
&lt;p&gt;가족 대부분이 애플 기기를 사용하고, 두 서비스에 동시에 용량 비용을 지불하려니 낭비인 것 같아서, 아마도 아이클라우드 포토만 계속 사용하고 구글 포토는 무료 용량으로 안드로이드에서 찍은 사진과 둥영상을 아이클라우드로 이동하는데에만 사용할 것 같습니다. 그렇다고 구글 원(Google One) 멤버십이 너무 비싼 것도 아니지만, 한국에선 왠지 반쪽짜리 서비스를 운영하는 것같은 기분이 들어서, 취소하고 무료 용량만 사용하는게 맞는 듯 합니다.&lt;/p&gt;
&lt;p&gt;거기에다가 이런 생태계에서 “탈출”하는 것을 날마다 힘들게 만들고 있으니, 둘 중 하나를 고르면 대부분 오래 쓰게 될 듯 합니다. 그래서 만약 가족이 한 생태계에 갇혀 있다면, 다 같이 “탈출”하는게 매우 귀찮아서 다른 플랫폼으로 옮기는 것이 힘들어집니다.&lt;/p&gt;
&lt;p&gt;구글 포토에 사진/둥영상 관리 기능이 많은 것은 사실입니다. 제 경험상으르도 사용하는게 엄청 편리했습니다. 하지만 구글 포토를 사용하면서 구글이라는 업체를 믿을 수 있는지 계속 의문점이 들어서 사용하는 것이 조금 꺼림칙했습니다. 좀 유별난게 아닌가 하실수도 있지만, 구글이 원래는 광고업체이다 보니, 제 사진을 가지고 인공지능 알고리즘을 가르치는게 좀 싫더라고요.&lt;/p&gt;
&lt;p&gt;아이클라우드 포토에서 이런 문제가 완전히 해결되는 것은 아닙니다. 그냥 주체가 구글에서 애플로 변경되는 것이죠. 애플이 만약 어느날 “사생활 보호”라는 명분을 아예 없애버리고 개인정보를 마구잡이로 쓴다면 문제가 되겠지만, 아직까진 구글보단 더 신뢰가 갑니다. 물론 일반인은 이런 것은 전혀 신경을 쓰지 않습니다. &lt;em&gt;그냥 사진하고 둥영상을 자동으로 백업해주는데 사생활 그런거 걱정할게 뭐가 있다는 거야?&lt;/em&gt; 라고 생각하죠.&lt;/p&gt;
&lt;p&gt;그래도 개인적으로 아이클라우드 포토를 추천하고 싶은게, 사진 백업도 아이클라우드 포토가 쉽기 때문입니다. 만약 내일 구글과 애플의 서버가 전부 사라지면 사진도 다 사라지기에, 온라인으로 사진이 동기화되어도 스스로 백업을 하는 것이 중요합니다. 맥을 사용한다면 타임 머신을 활성화시키고 “원본 보관” 옵션을 키면 맥이 자동으로 사진을 또 백업해줍니다. 구글은 데스크탑 어플 하나 없고, 아까 보았듯이 제대로 된 내보내기 옵션은 존재하지도 않아 백업이 불가능하기에, 이 부분에선 아이클라우드가 좋은 것 같습니다. 특히, 사진을 원본 그대로 보존하고 싶다면 더더욱 구글 포토가 불리해집니다.&lt;/p&gt;
&lt;p&gt;종합적으로 고려한다면 둘 다 괜찮지만, 한번 선택하면 바꾸는 것이 매우 힘들기에 신중하게 결정하세요!&lt;/p&gt;
&lt;h1 id=&quot;둘-다-쓰면-안돼요&quot;&gt;둘 다 쓰면 안돼요?&lt;/h1&gt;
&lt;p&gt;쓸 수 있지만, 염두해 두어야 할 사항이 몇 가지 있습니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;구글 포토를 한 애플 기기에만 설치해야 합니다. 만약 여러 대에서 구글 포토를 보고 싶으시다면 딱 한 대의 기기에서만 자동 백업 기능을 켜야 됩니다.&lt;/li&gt;
&lt;li&gt;자동 백업 기능을 킨 기기에서는 아이클라우드 설정에서 “원본 보관” 옵션을 선택해야 구글에 원본 사진이 업로드됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;여기에서 단점이 있는데, 안드로이드에서 찍은 사진이나 구글 포토에 직접 업로드한 사진은 아이클라우드 사진으로 자동으로 넘어가지 않기에, 어쩌다가 한번씩 수동으로 구글 포토에서 사진을 다운받아 동기화시켜야 합니다.&lt;/p&gt;
&lt;p&gt;만약 자동 백업 기능을 비활성화한다면, 반대로 가는 사진도 업로드되지 않지만, 이전에 업로드되었던 사진은 보존됩니다. 아직은 안드로이드에서 아이클라우드 사진을 볼 순 없지만, 데스크탑에선 웹 버전으로 아이클라우드 포토에서 사진을 볼 수 있습니다.&lt;/p&gt;</content:encoded></item><item><title>둥영상 공유하기 전 처리하기</title><link>https://ericswpark.com/ko/blog/2021/2021-02-28-things-to-do-before-sharing-videos/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2021/2021-02-28-things-to-do-before-sharing-videos/</guid><pubDate>Sun, 28 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;둥영상-압축&quot;&gt;둥영상 압축&lt;/h1&gt;
&lt;p&gt;카카오톡에서 둥영상을 전송할 때 파일이 300 MB을 초과하면 전송에 실패합니다. 제가 보낼려고 했던 둥영상의 크기가 4K 화질에 418 MB였는데, 카카오톡에서는 코덱이 마음에 들지 않던지 자동으로 변환을 해 주지 않았습니다. 다행히도 &lt;code&gt;ffmpeg&lt;/code&gt;를 사용하면 둥영상을 압축할 수 있습니다.&lt;/p&gt;
&lt;p&gt;다음을 실행하면 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ffmpeg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; video.mp4&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -crf&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 24&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-crf&lt;/code&gt; 플래그 다음에 오는 숫자를 조절하면서 화질과 파일 크기를 확인하세요. &lt;code&gt;24&lt;/code&gt;를 사용했을때 아까 둥영상의 크기는 122 MB 정도로 줄었습니다. &lt;code&gt;-crf&lt;/code&gt; 숫자가 클수록 크기가 작아지지만 화질이 나빠지고, 반대도 비슷하게 비례합니다.&lt;/p&gt;
&lt;h1 id=&quot;화질-줄이기&quot;&gt;화질 줄이기&lt;/h1&gt;
&lt;p&gt;만약 아직도 크기가 너무 크다면 화질을 줄여 크기를 더욱 낮출 수 있습니다. 다음 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ffmpeg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; video&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -vf&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; scale=xxx:xxx&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;xxx&lt;/code&gt;에다 새로운 화질 크기를 지정하면 됩니다. 원래의 화질이 이 새 크기에 비례해야 합니다. 예를 들어, 3840x2160 비디오를 1080p 화질로 낮출려면 두 숫자를 반으로 나눠 1920x1080으로 표기해야 합니다. 그럼 플래그 부분에 &lt;code&gt;scale=1920:1080&lt;/code&gt;을 지정하면 됩니다.&lt;/p&gt;
&lt;p&gt;화질을 줄인 후 둥영상의 크기는 36 MB로 줄어들었습니다. 이 정도면 보낼 때 빨리 전송되서 편리합니다.&lt;/p&gt;
&lt;h1 id=&quot;촬영-장소-정보-삭제&quot;&gt;촬영 장소 정보 삭제&lt;/h1&gt;
&lt;p&gt;둥영상을 휴대폰이나 GPS가 내장된 카메라로 찍었을 경우, 위치 정보가 내장되어 있을 수 있습니다. 집 주소나 다른 정보를 인터넷에 유출할 수 있기에 삭제하는 것이 좋습니다.&lt;/p&gt;
&lt;p&gt;여기에도 역시나 &lt;code&gt;ffmpeg&lt;/code&gt;를 사용합니다 (&lt;code&gt;ffmpeg&lt;/code&gt; 개발자분들 진짜 감사합니다 :D). 다음을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ffmpeg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; video.mp4&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -metadata&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; location=&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -metadata&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; location-eng=&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;새로 나온 둥영상에서는 위치 정보가 삭제되어 출력됩니다.&lt;/p&gt;</content:encoded></item><item><title>SSH 키 자동 업데이트하기</title><link>https://ericswpark.com/ko/blog/2021/2021-02-01-persistent-access-with-ssh-keys/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2021/2021-02-01-persistent-access-with-ssh-keys/</guid><pubDate>Mon, 01 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;새로 산 컴퓨터에 개발툴 설치하느라 하루 다 버린 다음에, 정작 개발할 때 SSH로 서버에서 하는 머저리 있습니다. 네, 바로 접니다. 근데, SSH 구동을 하니 여태까지 한 번도 물어보지 않았던 비밀번호를 물어봅니다. 아, SSH 키를 가져오는 것을 깜빡했습니다. 거기에다가 어떤 서버는 키가 없다면 접속도 막아버립니다. 하루는 다 날리고 개발은 하나도 못 하고. 생산성이 이렇게 바닥이니 면접에서 다 떨어지고 취직도 못 하고 집에서 멍청이 소리 듣고… 아니라고요?&lt;/p&gt;
&lt;p&gt;이런 참사를 막기 위해서 한 번 접속하면 영원히 접속할 수 있도록 SSH 키를 업데이트해주는 스크립트를 설치해 보겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;1-github&quot;&gt;1. GitHub&lt;/h1&gt;
&lt;p&gt;일단 GitHub에 접속해서 보안 설정이 완벽하게 설정되어 있는지 확인합니다. 만약 GitHub가 뚫리면 이 스크립트가 설치된 서버들 역시 뚫리게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/settings/security&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;따라서 GitHub 계정의 비밀번호를 변경하고 &lt;strong&gt;2FA를 활성화합니다.&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이제 &lt;a href=&quot;https://github.com/settings/keys&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Settings &amp;gt; SSH and GPG keys 아래에 새로운 SSH 키들을 모두 추가해줍니다&lt;/a&gt;. 만약, 나중에 새로운 SSH 키를 만든다면, 여기에 추가하면 스크립트에서 자동으로 불러옵니다.&lt;/p&gt;
&lt;p&gt;키 추가를 다 하셨다면 이제 연결할 서버로 이동합니다.&lt;/p&gt;
&lt;h1 id=&quot;2-스크립트&quot;&gt;2. 스크립트&lt;/h1&gt;
&lt;p&gt;스크립트를 설치할 서버에 연결 후, &lt;code&gt;update-ssh.sh&lt;/code&gt;라는 스크립트를 만들어 다음을 붙여넣습니다:&lt;/p&gt;
&lt;figure data-astro-cid-gao2j22i&gt; &lt;div data-astro-cid-gao2j22i&gt; &lt;iframe frameborder=&quot;0&quot; id=&quot;astro-gist-8be6357361c1cbb4de0818ea0f0d15ad-3c197akchgr&quot; data-astro-gist-iframe data-gist-link=&quot;https://gist.github.com/8be6357361c1cbb4de0818ea0f0d15ad.js&quot; data-gist-styles-url=&quot;/styles/gist.css&quot; data-lazy=&quot;true&quot; data-root-margin=&quot;150&quot; data-show-gist-link-on-error=&quot;true&quot; data-astro-cid-gao2j22i&gt;&lt;/iframe&gt; &lt;/div&gt;  &lt;/figure&gt;  &lt;script type=&quot;module&quot; src=&quot;/home/runner/work/ericswpark.github.io/ericswpark.github.io/node_modules/@kotosha/astro-gist/src/Gist.astro?astro&amp;type=script&amp;index=0&amp;lang.ts&quot;&gt;&lt;/script&gt;
&lt;p&gt;GitHub 아이디를 스크립트에 편집해넣은 다음, 저장합니다.&lt;/p&gt;
&lt;p&gt;이 스크립트를 실행하면 GitHub 프로필의 모든 공개 키를 끌어온 다음 &lt;code&gt;~/.ssh&lt;/code&gt; 폴더 안 &lt;code&gt;authorized_keys&lt;/code&gt; 파일에 저장합니다. 그 다음, 적절한 권한을 설정해 SSH 데몬이 파일을 읽을 수 있도록 합니다.&lt;/p&gt;
&lt;p&gt;스크립트에 실행 가능 비트를 추가하고 (&lt;code&gt;chmod +x update-ssh.sh&lt;/code&gt;) 실행합니다. 이제 새로운 키로 로그인을 시도하면 정상적으로 인식이 됩니다.&lt;/p&gt;
&lt;p&gt;하지만 이건 매번 이렇게 실행해야 변경이 되니, 자동으로 끌어오도록 변경해보도록 하겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;3-자동화&quot;&gt;3. 자동화&lt;/h1&gt;
&lt;p&gt;주기적으로 스크립트를 실행하려면 &lt;code&gt;crontab&lt;/code&gt;을 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;crontab&lt;/code&gt; 사용법을 모르신다고요? 그럼 빨리 짚고 넘어갑니다. 일반적인 &lt;code&gt;crontab&lt;/code&gt; 문법은 다음과 같습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ls&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;첫 번째 열은 분 단위(0&lt;del&gt;59)고, 두 번째 열은 시간 단위(0&lt;/del&gt;23)입니다. 셋째 열은 월일 단위(1&lt;del&gt;31)이고, 넷째 열은 월 단워(1&lt;/del&gt;12)이고, 마지막 열은 주일입니다(0은 일요일~6은 토요일). 와일드 카드 부호 (*)는 그 단위의 가능한 모든 값에 부합합니다.&lt;/p&gt;
&lt;p&gt;따라서, 위 &lt;code&gt;crontab&lt;/code&gt; 줄은 매시각 20분에 실행됩니다 (2시 20분, 3시 20분, 등등).&lt;/p&gt;
&lt;p&gt;또 반복 패턴도 있지만 (/2는 2 단위마다, 등등) 모든 리눅스 배포판에서 동작하진 않기에 사용할때 주의해야 합니다!&lt;/p&gt;
&lt;p&gt;리눅스 잘 쓰신다면 &lt;code&gt;systemd&lt;/code&gt;도 아실 겁니다. 하지만 제 생각엔 아직 &lt;code&gt;cron&lt;/code&gt;이 더 많이 사용되고 있는 것 같아서 (아닐수도 있습니다), 이 튜토리얼에선 &lt;code&gt;crontab&lt;/code&gt;을 사용합니다.&lt;/p&gt;
&lt;p&gt;접속된 서버에서 &lt;code&gt;crontab -e&lt;/code&gt;를 실행합니다. 기본 에디터가 열리면 맨 아래로 내려가 다음을 입력합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/update-ssh.sh&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/update-ssh.sh.log&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;숫자 42를 0과 59 사이의 임의의 숫자로 변경해 동시다발적으로 스크립트가 작동하지 않게 해 두 서버의 부하를 줄입니다. 이 &lt;code&gt;crontab&lt;/code&gt; 줄은 매 시간 설정된 분 단위에 실행됩니다.&lt;/p&gt;
&lt;h3 id=&quot;왜-1분마다-불러오면-안되나요&quot;&gt;왜 1분마다 불러오면 안되나요?&lt;/h3&gt;
&lt;p&gt;GitHub의 서버나 접속하는 서버에 필요없는 부하를 줄이기 위해서입니다. SSH 키를 그렇게 자주 바꾸는 것도 아니지만, 너무 느리면 그냥 수동으로 하는게 더 편리하기에 한 시간이 그나마 적당합니다. 더 작은 주기로 설정할 수 있지만 GitHub에서 접속 제한을 걸 수도 있습니다.&lt;/p&gt;
&lt;p&gt;로그를 남기기 위한 파이프(&amp;gt;)는 꼭 필요하진 않지만 나중에 키 추가 기록에 유용할 수도 있습니다.&lt;/p&gt;
&lt;h1 id=&quot;결론&quot;&gt;결론&lt;/h1&gt;
&lt;p&gt;이제 SSH 키를 업데이트할 때마다 그냥 GitHub에 추가해두고 한 시간 정도를 기다리면 자동으로 스크립트가 설치되어 있는 모든 서버에 업데이트됩니다!&lt;/p&gt;</content:encoded></item><item><title>MSI 노트북 PD 충전 디자인 문제</title><link>https://ericswpark.com/ko/blog/2021/2021-01-11-msi-laptop-usb-c-charger-design-problem/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2021/2021-01-11-msi-laptop-usb-c-charger-design-problem/</guid><pubDate>Mon, 11 Jan 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;만약 최근에 USB-C 포트가 달려있는 MSI 노트북을 구매하셨다면 이 글이 유용할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;제가 얼마 전 구입한 Creator 15M 노트북이 어떤 USB-C 기기와는 연결되지 않는 문제를 발견했습니다. 유일하게 작동하는 기기는 제 외장 SSD (삼성 T5) 뿐이였고, 제 USB-C 허브와 휴대폰은 포트가 정상적으로 인식하질 않았습니다.&lt;/p&gt;
&lt;p&gt;이상하게도, 연결된 기기에 전원은 공급되었습니다. USB-C 허브는 연결 시 나타나는 LED 불이 켜졌고, 휴대폰(노트 20)은 “충전기 연결 확인” 문구를 표시했지만 “디바이스 데이터 접근 허용” 메시지는 표시되지 않았습니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;note-20-ultra-charging-message&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;270&quot; height=&quot;579&quot; src=&quot;/_astro/note-20-ultra-charging-message.CH7AXvMn_1zAmVa.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;처음에는 무슨 드라이버 문제인줄 알고 장치 관리자에서 몇 시간 동안 USB 관련 드라이버를 삭제해보고, Nvidia 드라이버 (타입-C 컨트롤러)도 건드려 보고, 이것저것 다 해봤습니다. 근데 드라이버 문제일 수가 없는게, 휴대폰을 일반 USB-A 포트에 타입-C 케이블로 연결하면 정상적으로 인식이 됩니다. 그래서 USB-C 포트 문제일 것이라고 생각했습니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;device-manager&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;992&quot; height=&quot;728&quot; src=&quot;/_astro/device-manager.CrvJSYnK_ZeXAK0.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;MSI의 고객센터에 전화를 걸었는데, 상담사 분이 매우 불친절하시더라고요. “그냥 전원만 공급하면 문제가 없다”라고 매우 성의없게 답변해주시고는 무슨 호환성 문제라고 하시더니 그냥 전화를 끊으셨습니다.&lt;/p&gt;
&lt;p&gt;그래서 할 수 없이 MSI 웹사이트에서 고객지원 티켓을 접수했는데, 답변에서 정답을 얻었습니다.&lt;/p&gt;
&lt;p&gt;결론은, &lt;strong&gt;기기 디자인 문제입니다.&lt;/strong&gt; MSI의 노트북의 USB-C 단자는 썬더볼트를 지원하지 않지만 (이건 상품 설명에 나와있기에 문제가 되질 않습니다), 거기에다가 USB-PD나 화면 출력을 지원하지 않습니다.&lt;/p&gt;
&lt;p&gt;타입-C 허브는 USB-C 포트로 노트북을 충전할 수 있는 PD 포트가 있고 HDMI 포트로 화면 출력이 되기에 단자에서 연결을 거부한 것이고, 휴대폰은 USB-PD를 지원하기에 노트북이 연결을 거절합니다.&lt;/p&gt;
&lt;p&gt;좀 이상하긴 하지만 제 경험이랑 일치하기 때문에 맞는 것 같습니다. 이 설명대로라면 외장 SSD는 USB-PD를 지원할 필요가 없어 기능이 아예 빠져있기에 연결이 잘 되는 것입니다. 이상하게 PD는 USB 전원 규격이고, PD 지원 여부에 따라서 데이터 선을 비활성화할 이유는 없는 것 같은데, 그래도 MSI측의 답변은 그렇다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;msi-support-team-reply&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1154&quot; height=&quot;661&quot; src=&quot;/_astro/msi-support-team-reply.CDPeMNAY_1et1Uf.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이 문제 때문에 노트북이 덜 유용해져서 꽤 불편합니다. Creator 15M 노트북은 타입-C 단자가 2개 있고 USB-A 단자가 2개 있는데, 이 문제로 타입-C 단자를 못 쓰게 된다면 단자가 부족해집니다. 거기에다가 요즘에 나오는 대부분의 기기는 USB-PD를 지원하는데, 이런 기기들을 다 연결하지 못한다면 들고 다녀야 되는 연결선이 늘어나 귀찮아집니다. 만약 소프트웨어 제약이라면 펌웨어 업데이트로 고쳐줬으면 하는데, 답변을 봐서는 하드웨어 디자인 문제 같으니 고쳐질 것 같지는 않습니다.&lt;/p&gt;</content:encoded></item><item><title>삼성 갤럭시 노트 3 네오 포팅하기</title><link>https://ericswpark.com/ko/blog/2020/2020-10-11-porting-samsungs-galaxy-note-3-neo/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2020/2020-10-11-porting-samsungs-galaxy-note-3-neo/</guid><pubDate>Sun, 11 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;지난 몇 주 동안, 삼성 갤럭시 노트 3 네오에 TWRP와 커스텀 롬 포팅을 시도해봤습니다. 일단 현재로선 출시되어 있는 롬과 TWRP 빌드는 모두 패치롬(.zip을 언패킹해서 수동으로 편집한 빌드)이라, 기기에선 매우 불안정하게 작동합니다. 거기에다가 TWRP 빌드는 나온지 엄청 오래되었고 (킷캣 정도 때 포팅된 2.8.7.0 버전) 포팅도 잘 안되어 있어 그래픽과 버튼이 모두 망가져 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;팁 - 컴파일한 롬은 패치롬과 달리 매번 빌드마다 확인할 수 있는 결과물이 나오기에 안정적이고 훨씬 좋습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 글에선 포팅을 시도하면서 알아낸 것을 간략하게 적어보겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;커널-포팅하기-찾기&quot;&gt;커널 &lt;del&gt;포팅하기&lt;/del&gt; 찾기&lt;/h1&gt;
&lt;p&gt;사실 포팅을 하면서 실수로 시간을 조금 낭비했는데, 커널을 만드려면 삼성에서 제공하는 GPL 덤프를 사용해서 빌드해야 하는 줄 알고 무슨 커널 커밋을 삼성이 사용했는지 확인하는데 시간을 많이 허비했습니다. 삼성이 제공하는 GPL 덤프는 tarball 파일이라, &lt;code&gt;git&lt;/code&gt; 커밋 정보가 아예 없어서 스크립트로 무슨 커밋을 사용했는지 일일히 확인해야 되는데, 리눅스 커널이 커밋량이 많이 아렇게 찾는데 시간이 매우 오래 걸립니다.&lt;/p&gt;
&lt;p&gt;운 좋게도, 같이 Team Bliss에서 일하고 있는 @Jackeagle이 &lt;a href=&quot;https://github.com/LineageOS/android_kernel_samsung_msm8974/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;LineageOS에서 이미 삼성 MSM8974 기기를 위한 커널 소스가 있다고 알려줬습니다.&lt;/a&gt; 그냥 이미 누군가 커밋을 다 찾아놓았네요. 그냥 가져와서 씁니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;팁 - 거의 대부분의 SoC 커널 (예를 들어 &lt;code&gt;samsung_msmxxxx&lt;/code&gt; 또는 &lt;code&gt;oneplus_msmxxxx&lt;/code&gt;)은 기기와 호환되기에, 커밋을 대립 방식으로 찾기 전에 먼저 SoC 커널이 있는지 확인하세요!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그래서 &lt;a href=&quot;https://github.com/ericswpark/android_kernel_samsung_msm8974/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;위에 있는 repo를 fork한 다음,&lt;/a&gt; &lt;code&gt;lineage_hltekor_defconfig&lt;/code&gt; 파일을 &lt;code&gt;lineage_frescoltekor_defconfig&lt;/code&gt;로 복사해서 수정하기 시작했습니다.&lt;/p&gt;
&lt;h1 id=&quot;defconfig-수정하기&quot;&gt;defconfig 수정하기&lt;/h1&gt;
&lt;p&gt;다음으로 &lt;code&gt;frescoltekor&lt;/code&gt; defconfig에서 몇 가지 설정을 수정했습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CONFIG_SEC_FRESCO_PROJECT=y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CONFIG_SEC_LOCALE_KOR_FRESCO=y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CONFIG_MACH_FRESCOLTEKTT=y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 설정을 키면, 예전에 다른 기기(여기에서 사용한 예전 기기는 &lt;code&gt;hltekor&lt;/code&gt;)를 위한 설정은 &lt;strong&gt;끄는 것&lt;/strong&gt;을 기억하세요.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# CONFIG_SEC_H_PROJECT is not set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# CONFIG_SEC_LOCALE_KOR_H is not set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# CONFIG_MACH_HLTEKTT is not set&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;팁 - 삼성 기기에서는 &lt;code&gt;X_PROJECT&lt;/code&gt; 설정 플래그도 있는데, 이것도 기기에 따라서 활성화해주거나 비활성화해야 합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그런 다음, 한 번 컴파일을 실행했는데 바로 실패해버렸습니다. 에러가 I2C 쪽에서 나오는 것 같은데, Team Bliss의 @pivcer분이 정확한 NFC I2C 플래그를 활성화하라고 알려주셨습니다. 그래서 &lt;code&gt;hltekor&lt;/code&gt; 플래그는 비활성화시키면서 동시에 알맞은 값을 설정해주었습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# CONFIG_BCM2079X_NFC_I2C is not set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CONFIG_SEC_NFC_I2C=y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# CONFIG_SEC_NFC is not set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CONFIG_NFC_PN547=y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;팁 - 에러 메시지를 읽으면서, 무슨 변수가 없는지, 무슨 부분이 이중으로 활성화되어 있는지 확인하세요. 그런 다음 &lt;code&gt;grep -r &quot;변수_이름&quot; .&lt;/code&gt;를 사용해 소스의 무슨 모듈에 코드가 있는지 확인한 다음, 무슨 플래그를 defconfig에서 활성화/비활성화해야 하는지 확인하세요.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;빌드를 또 했는데, 또 실패했습니다.&lt;/p&gt;
&lt;p&gt;이상하게도 빌드 시스템이 변수 하나를 못 찾는 것 같습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CC&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      init/version.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  LD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      init/built-in.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  LD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      .tmp_vmlinux1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 6&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; arch/arm/crypto/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 28&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; arch/arm/mach-msm/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 55&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; mm/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 40&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; fs/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 15&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; crypto/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 36&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; block/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 22&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lib/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 24&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; drivers/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 6&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; sound/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 6&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; arch/arm/oprofile/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 37&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; net/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:leds-max77803.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_get_en_value.part.0:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;led_flash_en&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:leds-max77803.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_get_en_value.part.0:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;led_torch_en&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:leds-max77803.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_probe:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;led_torch_en&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:leds-max77803.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_probe:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;led_flash_en&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:leds-max77803.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_probe:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;camera_class&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:temphumidity_shtc1.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_en:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;led_flash_en&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:temphumidity_shtc1.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_en:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;led_torch_en&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:leds-max77803.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_remove:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;camera_class&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Makefile:889:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; recipe&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; target&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;.tmp_vmlinux1&apos;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; failed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;make:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ***&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [.tmp_vmlinux1] Error 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ericswpark@buildbox:~/android_kernel_samsung_msm8974$&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 문제를 디버깅하느라 시간을 많이 썼는데, 결국 한 모듈이 제대로 빌드되지 않는 것을 확인했습니다. 이 모듈은 퀄콤 MSM 카메라 모듈인데, 설정 파일에서 제 기기의 플래그를 빼먹는 바람에 컴파일이 제대로 이루어지지 않았습니다. &lt;a href=&quot;https://github.com/ericswpark/android_kernel_samsung_msm8974/commit/c07074108716e060cbe3c8fae50d85fea7015690&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;기기 플래그를 추가했더니 정상적으로 컴파일이 돌아갑니다.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;팁 - 어쩔 땐 커널 트리가 고장난 상태로 배포되는 경우가 종종 있습니다. OEM에서 오는 GPL 덤프라서, 또는 롬 팀에서 많이 쓰는 커널 트리라고 반드시 문제가 없는 것은 아니기에, 위의 예시처럼 부러진 부분에 대해서 직접 패치를 해야 되는 경우도 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그래서 패치한 후 다시 빌드를 돌려보니 &lt;code&gt;zImage&lt;/code&gt; 파일이 정상적으로 생성됐습니다.&lt;/p&gt;
&lt;h1 id=&quot;twrp-포팅하기&quot;&gt;TWRP 포팅하기&lt;/h1&gt;
&lt;p&gt;이제 비슷한 방법으로 TWRP 포팅을 시도했습니다. &lt;code&gt;hltekor&lt;/code&gt; 기기 트리를 fork한 후, 그 repo 위에 변경사항을 추가했습니다. 이전 단계에서 컴파일한 커널을 복사한 후, 빌드를 시작했습니다.&lt;/p&gt;
&lt;p&gt;만약 똑같이 해 보시면 오류가 발생할 수도 있는데, 그럼 Makefile에서 설정값을 몇 가지 변경하면서 오류를 수정하면 됩니다.&lt;/p&gt;
&lt;p&gt;이 부분이 가장 지루한데, 저는 너무 결과물이 안 나와서 몇 주 정도 걸렸습니다. 마침내 빌드가 성공하면 엄청 뿌듯해집니다. (근데, 사실 아까 카메라 패치가 가장 뿌듯한것 같습니다. ^^)&lt;/p&gt;
&lt;h1 id=&quot;지금으로선-끝&quot;&gt;(지금으로선) 끝&lt;/h1&gt;
&lt;p&gt;빌드 후, 컴파일된 이미지를 플래시해봤는데, 아쉽게도 폰이 새로운 커널은 인식하지 않고 부트루프에 빠져버립니다.&lt;/p&gt;
&lt;p&gt;지금 추측할 수 있는 건 아마도 &lt;code&gt;hltekor&lt;/code&gt;의 터치스크린 드라이버가 노트 3 네오 하드웨어와 충돌하는 것 같습니다. 왜냐하면 노트 3 네오는 720p 패널을 탑재했는데, 노트 3 (&lt;code&gt;hltekor&lt;/code&gt;)은 1080p 패널을 탑재해, 두 폰 사이에 터치스크린 칩이나 패널 구성이 달라서 문제가 생기는 걸 수도 있습니다.&lt;/p&gt;
&lt;p&gt;디버깅을 해보기 위해서 기기에서 &lt;code&gt;/proc/last_kmesg&lt;/code&gt;를 다운받았는데, 유용한 정보는 표시되지 않았습니다. 커널 자체가 로드가 안 되는 것 같은데, 이게 참 이상한게 드라이버 문제가 발생하였더라도 기본 커널 자체가 부팅이 안 될 이유가 없기에 왜 부팅이 안 되는지 이해가 되질 않습니다. 디버그 케이블로 serial 연결을 할 수 있는지 확인했는데, 지금 납땜 도구가 없어서 일단 기다려야 될 것 같습니다.&lt;/p&gt;
&lt;p&gt;그래서 일단 오늘 글은 여기까지만 적겠습니다. 만약 직접 해 보고 싶으시다면 기기와 커널 트리는 모두 제 깃허브에서 확인하실 수 있습니다. 만약 문제를 발견하셨다면 이메일로 연락주세요!&lt;/p&gt;
&lt;p&gt;이 글을 쓰는 데 도와주신 MSM8974 커널 개발자분과 LineageOS &lt;code&gt;hltekor&lt;/code&gt; 개발자분들께, 그리고 텔레그램 리눅스 커널 채팅방과 Team Bliss의 팀 멤버들께 감사의 말씀을 드립니다.&lt;/p&gt;</content:encoded></item><item><title>안드로이드 커널 컴파일 시 올바른 툴체인 사용하기</title><link>https://ericswpark.com/ko/blog/2020/2020-10-05-android-kernel-use-the-proper-toolchain/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2020/2020-10-05-android-kernel-use-the-proper-toolchain/</guid><pubDate>Mon, 05 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안드로이드 커널을 소스에서 컴파일할 때 다음과 같은 오류가 발생했습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  LD      drivers/net/built-in.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  LD      drivers/usb/gadget/g_android.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  LD      drivers/usb/gadget/built-in.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  LD      drivers/usb/built-in.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  LD      drivers/built-in.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  LD      vmlinux.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  MODPOST vmlinux.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ERROR: modpost: Found 13 section mismatch(es).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;To see full details build your kernel with:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&apos;make CONFIG_DEBUG_SECTION_MISMATCH=y&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;To build the kernel despite the mismatches, build with:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&apos;make CONFIG_NO_ERROR_ON_MISMATCH=y&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(NOTE: This is not recommended)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/home/users/ideaman924/android_kernel_samsung_msm8974/scripts/Makefile.modpost:98: recipe for target &apos;vmlinux.o&apos; failed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;make[1]: *** [vmlinux.o] Error 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Makefile:947: recipe for target &apos;vmlinux.o&apos; failed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;make: *** [vmlinux.o] Error 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ideaman924@build:~/android_kernel_samsung_msm8974$&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이상하게도 다른 &lt;code&gt;defconfig&lt;/code&gt;이나 커널 소스를 교체해도 다 똑같이 이상한 &lt;code&gt;modpost section mismatch&lt;/code&gt; 에러가 발생했습니다. 왜 이렇게 문제가 발생하는지 검색하면서 다음 Stack Overflow 글들을 확인했지만 별로 도움이 되진 않았습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/27698976/how-to-fix-section-mismatch-errors-during-cross-compile-of-android&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;How to fix section mismatch errors during cross compile of android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/6807766/linux-kernel-config-debug-section-mismatch-make-errors&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Linux kernel CONFIG_DEBUG_SECTION_MISMATCH make errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/8563978/what-is-kernel-section-mismatch&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;What is kernel section mismatch?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;근데, 이 커널 소스는 LineageOS에서 사용하는 커널 소스라, 설마 LineageOS가 잘못된 코드를 배포하진 않을거라 생각해서 다른 해결방안을 찾아봤습니다.&lt;/p&gt;
&lt;p&gt;문제는 코드가 아니라 툴체인이였는데, 텔레그램상의 리눅스 커널 채팅방에서 @mochi_wwww라는 유저가 제가 잘못된 툴체인을 사용하고 있다는 점을 알려줬습니다. 제가 사용하던 &lt;a href=&quot;https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;툴체인은 몇 년 동안 방치되어 있던 툴체인이였습니다.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;사실 이게 헷갈렸던 이유는, &lt;a href=&quot;https://forum.xda-developers.com/android/software-hacking/reference-how-to-compile-android-kernel-t3627297&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;레퍼런스 안드로이드 커널 컴파일 가이드가&lt;/a&gt; 그 오래된 툴체인에 링크하기에 그 툴체인을 써도 괜찮은 줄 알았습니다. &lt;a href=&quot;https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;제대로 된 툴체인을 사용하니 바로 문제가 해결됐습니다.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;그러니 만약 위에 나온 에러가 커널 컴파일 중 발생한다면, 저처럼 괜히 고생하면서 시간 낭비하시지 마시고, 커널에 알맞는 툴체인을 사용해서 컴파일을 해 보세요. LineageOS에서 나온 커널 소스라면 &lt;code&gt;AndroidKernel.mk&lt;/code&gt; 파일에 대부분 툴체인 정보가 나와 있습니다.&lt;/p&gt;</content:encoded></item><item><title>안드로이드 기기 안전하게 지우기</title><link>https://ericswpark.com/ko/blog/2020/2020-09-28-securely-wiping-android-devices/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2020/2020-09-28-securely-wiping-android-devices/</guid><pubDate>Mon, 28 Sep 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;이 글은 2021년 1월 26일에 수정되었습니다. (awipe 프로젝트 정보 추가)&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;오래된 안드로이드 기기를 중고시장에 판매할 때 개인정보가 유출되지 않도록 기기를 지워야 됩니다. 만약 기기의 플래시 펌웨어가 제대로 작성되어 있다면, 리커버리에서 데이터 파티션을 포맷할 때 정보가 완전히 지워지도록 되어 있습니다. 하지만 모든 기기의 펌웨어가 이렇게 작동한다고 보장할 수 없기에 다른 방식으로 기기를 지워야 합니다.&lt;/p&gt;
&lt;p&gt;문제는, 인터넷에 나와있는 대부분의 글들이 서로 상충합니다. 어떤 분들은 그냥 리커버리에서 공장 초기화를 진행하면 충분하다고 하지만, 어떤 분들은 초기화 전 기기를 암호화해야 기기의 비어있는 공간까지 전부 암호화되어 지워지기 때문에 암호화해서 기기를 지워야 한다고 합니다. 거기에다가 데이터가 특히 중요한 정보라면, 기기 파괴밖에 답이 없다고 하네요.&lt;/p&gt;
&lt;p&gt;누가 맞고 틀린지 모르는 상황에서, 안드로이드 기기를 초기화시키는 방법을 가장 편집적인 순위로 나열해봤습니다!&lt;/p&gt;
&lt;h1 id=&quot;1---그냥-지우기&quot;&gt;1 - 그냥 지우기&lt;/h1&gt;
&lt;p&gt;만약 기기가 최근에 출시된 기기고, 공장에서 암호화되어 출고되었다면 그냥 지우셔도 충분합니다.&lt;/p&gt;
&lt;p&gt;안드로이드 10을 달고 나오는 기기는 &lt;a href=&quot;https://source.android.com/security/encryption/file-based&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;FBE (file-based encryption, 각 파일 암호화 방식)&lt;/a&gt;를 지원해야 하기에 이렇게 초기화하면 암호화된 데이터만 남기에 개인정보가 파기됩니다.&lt;/p&gt;
&lt;p&gt;만약 기기가 FBE로 암호화되어 있는지 확인하고 싶다면 다음 프롭 (prop)을 확인합니다. &lt;code&gt;ro.crypto.state&lt;/code&gt;과 &lt;code&gt;ro.crypto.type&lt;/code&gt;가 &lt;code&gt;file&lt;/code&gt;로 설정되어 있어야 합니다.&lt;/p&gt;
&lt;h1 id=&quot;2---암호화하고-지우기&quot;&gt;2 - 암호화하고 지우기&lt;/h1&gt;
&lt;p&gt;만약 기기가 최근 몇 년 안에 출시된 기기고 &lt;a href=&quot;https://source.android.com/security/encryption/full-disk&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;FDE (full-disk encryption, 전체 디스크 암호화 방식)&lt;/a&gt;을 지원한다면 암호화하고 지우시면 됩니다.&lt;/p&gt;
&lt;p&gt;만약 기기가 FDE로 암호화되어 있는지 확인하고 싶다면 다음 프롭 (prop)을 확인합니다: &lt;code&gt;ro.crypt.state&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id=&quot;31---dd&quot;&gt;3.1 - &lt;code&gt;dd&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://www.reddit.com/r/Android/comments/1v568y/for_those_who_want_to_securely_wipe_their_android/cep413z?utm_source=share&amp;#x26;utm_medium=web2x&amp;#x26;context=3&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;원본 레딧 글&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;위에 링크된 글을 읽으면서, 거의 대부분의 안드로이드 기기가 Busybox와 함께 출고된다는 것을 기억했습니다. 이렇게 딸려 오는 Busybox에는 &lt;code&gt;dd&lt;/code&gt;라는 유틸리티가 포함되어 있는데, 이 툴을 사용해서 데이터 복구를 막을 수 있습니다.&lt;/p&gt;
&lt;p&gt;기기의 모든 블록을 지우기 위해 시스템의 랜덤 데이터 스트림과 &lt;code&gt;dd&lt;/code&gt; 유틸리티로 랜덤 파일을 만듭니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;adb shell&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cat /dev/urandom &gt; random_file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 원글 게시자처럼 편집증이 심하시다면 여러 번 돌리셔도 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for i in 0 1 2 3 4 5 6 7 8 9; do cat /dev/urandom &gt; $i; rm $i; done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;사실, 이렇게 많이 돌리는 건 쓸데없는 것 같습니다. 플래시 저장소는 일정한 쓰기 사이클 (write cycle) 수를 초과하면 고장날 수 있고, 플래시 저장소의 여분 셀에서 데이터를 복구하는 것도 확률이 매우 낮은 걸로 알고 있습니다.&lt;/p&gt;
&lt;h1 id=&quot;32---앱-사용&quot;&gt;3.2 - 앱 사용&lt;/h1&gt;
&lt;p&gt;위에 있는 유틸리티를 사용하기 쉽게 어플로 한번 제작해봤습니다.&lt;/p&gt;
&lt;p&gt;플레이 스토어에 가면 이렇게 랜덤 데이터를 가지고 똑같이 메모리를 덮어쓰기하는 어플이 많이 있는데, 전 소스 코드가 공개되어 있지 않아 그렇게 믿을 수가 없어 그냥 직접 만들었습니다.&lt;/p&gt;
&lt;p&gt;어플의 이름은 &lt;a href=&quot;https://github.com/ericswpark/awipe/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;awipe&lt;/a&gt;이고, 작동 방식은 위에 있는 &lt;code&gt;dd&lt;/code&gt; 툴처럼 랜덤 데이터를 가지고 내부 저장소에 기록합니다.&lt;/p&gt;
&lt;p&gt;다른 어플이나 지우기 방식보다 이 어플이 좋은 이유는:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;USB 디버깅을 활성화할 필요가 없습니다.&lt;/li&gt;
&lt;li&gt;컴퓨터 없이 지우기가 가능합니다.&lt;/li&gt;
&lt;li&gt;OTG와 USB로 어플 설치가 가능해서, 플레이 스토어에 구글 계정으로 로그인할 필요가 없습니다. 그냥 &lt;code&gt;.apk&lt;/code&gt; 파일을 다운받아 한번 돌리고 공장 초기화를 진행하면 기기에 남는 개인정보가 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dd&lt;/code&gt; 툴과는 다르게 진행 상황을 보여줍니다.&lt;/li&gt;
&lt;li&gt;오픈 소스 어플입니다!&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dd&lt;/code&gt; 툴의 사용 커맨드를 안 외워도 됩니다.&lt;/li&gt;
&lt;li&gt;기기 상에서 동작하기에 별도 특별한 장비가 필요하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;4---물리적-파괴&quot;&gt;4 - 물리적 파괴&lt;/h1&gt;
&lt;p&gt;만약 진짜 중요한 정보가 저장되어 있는 기기고, 중고장터에 팔 필요가 없다면 그냥 물리적으로 기기를 파괴해도 됩니다.&lt;/p&gt;
&lt;p&gt;만약 기기를 분해할 수 있고 다른 부품은 재활용하고 싶으시다면 보드상의 플래시 칩만 파괴하면 됩니다. iFixit이나 유튜브의 분해영상을 보고 칩이 어디 있는지 확인해서 드릴로 구멍을 박으면 복구가 엄청 어려워집니다. 아니면, 배터리를 분리해서 버린 다음 기기만 불로 태워버리면 됩니다. (물론, 한국에선 개인 소각이 불법이지만, 영문 버전에서 나온 방법이기 때문에 여기에 간략하게 적습니다.)&lt;/p&gt;</content:encoded></item><item><title>맥북프로 써멀구리스 재도포</title><link>https://ericswpark.com/ko/blog/2020/2020-05-18-repasting-my-macbook-pro/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2020/2020-05-18-repasting-my-macbook-pro/</guid><pubDate>Mon, 18 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;원래 이 글을 적을 때 설명도 많이 하고 사진도 많이 찍으려고 했는데, 한번 해보고 나서 다시는 하기 싫어져 그냥 글로만 적습니다.&lt;/p&gt;
&lt;p&gt;나사 종류도 4가지가 넘고, 연결 케이블이 엄청나게 많아서 분해가 힘듭니다. 게다가 팬을 분리하기 위한 나사는 메인보드 반대편에 있어서, 써멀구리스를 재도포하기 위해선 노트북을 완전히 분해해야 됩니다.&lt;/p&gt;
&lt;p&gt;그래서 설명은 없지만, 분해하면서 찍은 사진은 여기 하나 남겨두겠습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;macbook-pro-internals&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;768&quot; src=&quot;/_astro/macbook-pro-internals.BdgNt3Kl_Z2frflT.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;참 애플은 노트북 내장 하나는 예쁘게 만드네요 😀&lt;/p&gt;
&lt;p&gt;원래 도포되어 있던 써멀구리스는 완전히 말라비틀어져서 맥북이 작업 없이 약 49도에서 돌아갔습니다. 도포후 다시 확인해보니 40에서 44도 정도에서 작동해, 재도포가 약 5도 정도 낮춰주었습니다.&lt;/p&gt;
&lt;p&gt;만약 도전해보고 싶으시다면 여기에 나사 정리용 페이지가 있습니다 (영어 버전으로만…):&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;screw-organizer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2480&quot; height=&quot;3504&quot; src=&quot;/_astro/screw-organizer.DsRxjgB-_ZS8yOY.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;나사 종류가 엄청 많은데, P5은 펜타로브 (Pentalobe) 규격이고, T로 시작하는 타입은 모두 별나사 (Torx) 규격입니다.&lt;/p&gt;
&lt;p&gt;나사를 분리 후 페이지에 &lt;strong&gt;정확하게&lt;/strong&gt; 붙이는 것이 매우 중요한데, 그 이유는 각 항목에서 분리하는 나사의 길이가 모두 다르기 때문입니다. 특히 1번, 6번, 7번, 그리고 10번의 나사의 길이가 다릅니다. 블루택이라는 재사용 풀 같은 걸 사용해서 붙이면 나사를 잃어버리지 않고 재조립하실 수 있습니다.&lt;/p&gt;
&lt;p&gt;그리고 연결 케이블은 찢기 쉽기 때문에 분리하거나 조립하실 때 찢지 않게 조심하세요!&lt;/p&gt;</content:encoded></item><item><title>워드프레스 사이트 (수동으로) 백업하기</title><link>https://ericswpark.com/ko/blog/2020/2020-04-04-backup-your-wordpress-site-manually/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2020/2020-04-04-backup-your-wordpress-site-manually/</guid><pubDate>Sat, 04 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;자동화된 솔루션으로 백업하지 말고, 수동으로 제대로 백업하세요!&lt;/p&gt;
&lt;p&gt;일단 임시 폴더를 생성해서 작업합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup-20200105&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup-20200105&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 MySQL 데이터베이스를 파일로 저장합니다!&lt;/p&gt;
&lt;p&gt;참고로 이 가이드에선 서버에 설치된 MySQL 설정이 유닉스 소켓을 사용해서 인증하는 것을 가정합니다. 만약 비밀번호 방식의 인증으로 MySQL 데이터베이스에 연결하고 MySQL 데이터베이스가 워드프레스가 설치되어 있는 서버에 같이 설치되어 있다면 그냥 유닉스 소켓 방식의 인증으로 변경하시는 것을 추천합니다. 추가 비밀번호를 기억할 필요가 없고 나중에 관리가 편리하기 때문입니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; mysqldump&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --add-drop-table&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; wordpress&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; database.sql&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 sudo 비밀번호를 물어본다면 입력하면 됩니다. 만약 MySQL 비밀번호를 물어본다면, 위의 글을 참고하세요.&lt;/p&gt;
&lt;p&gt;이제 웹 사이트의 정적 파일을 복사해올 차례입니다. 다음을 실행하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;cp&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -a&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /var/www/example.com&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-a&lt;/code&gt; 플래그는 &lt;code&gt;cp&lt;/code&gt; 프로그램에게 모든 파일 플래그(attribute)를 복사하라고 지시합니다. 가끔 실패하는 경우가 있는데, 제가 실행했을 때엔 파일 소유자 정보를 &lt;code&gt;www-data&lt;/code&gt;에서 &lt;code&gt;ericswpark&lt;/code&gt;로 변경해버렸습니다. 이땐 &lt;code&gt;chown&lt;/code&gt;으로 수정해주면 됩니다.&lt;/p&gt;
&lt;p&gt;마지막으로 &lt;code&gt;tar&lt;/code&gt;을 사용해 폴더를 압축해줍니다. 한 단계 이전으로 나온 다음 다음을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tar&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -czvf&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup-20200105.tar.gz&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup-20200105/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 생성된 &lt;code&gt;tarball&lt;/code&gt; 파일을 백업하면 됩니다.&lt;/p&gt;
&lt;h1 id=&quot;복원&quot;&gt;복원&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;tarball&lt;/code&gt; 파일을 다운받은 후, 압축해제합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tar&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -xzvf&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup-20200105.tar.gz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;폴더로 진입한 다음, 다음 명령으로 복원합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; mysql&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; wordpress&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; database.sql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;cp&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -a&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; example.com&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /var/www/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 권한 문제가 발생하면 &lt;code&gt;chown&lt;/code&gt;/&lt;code&gt;chmod&lt;/code&gt;로 수정하면 됩니다.&lt;/p&gt;</content:encoded></item><item><title>GPG 완전정리 가이드</title><link>https://ericswpark.com/ko/blog/2020/2020-02-25-gpg-the-complete-crash-course/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2020/2020-02-25-gpg-the-complete-crash-course/</guid><pubDate>Tue, 25 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;키-만들기&quot;&gt;키 만들기&lt;/h1&gt;
&lt;p&gt;키를 만드려면 다음을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --gen-key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 GPG가 여러 번 질문하지 않고 그냥 키를 만들기 시작한다면, 시스템에 따라 구동 방식에 차이가 있을 수 있어 다음을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --full-generate-key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정상적인 출력은 다음과 같습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg (GnuPG) 2.2.17; Copyright (C) 2019 Free Software Foundation, Inc.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;This is free software: you are free to change and redistribute it.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;There is NO WARRANTY, to the extent permitted by law.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: directory &apos;/Users/ericswpark/.gnupg&apos; created&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: keybox &apos;/Users/ericswpark/.gnupg/pubring.kbx&apos; created&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please select what kind of key you want:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (1) RSA and RSA (default)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (2) DSA and Elgamal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (3) DSA (sign only)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (4) RSA (sign only)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Your selection?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;엔터를 눌러 진행합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RSA keys may be between 1024 and 4096 bits long.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;What keysize do you want? (2048)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;키 사이즈는 &lt;code&gt;4096&lt;/code&gt; 비트로 설정해도 괜찮습니다. 엔터를 눌러 진행합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please specify how long the key should be valid.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        0 = key does not expire&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;  = key expires in n days&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;w = key expires in n weeks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;m = key expires in n months&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;y = key expires in n years&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Key is valid for? (0)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기에서 보안성을 높이려면 키 유효기간을 설정하는 것이 좋습니다. 하지만, 만약 이 귀찮은 과정을 매년 반복하기 싫으시다면 GnuPG는 서브키라는 기능이 있기에 그걸 사용하시면 됩니다. 나중에 서브키에 대해서 더 설명드릴테니 여기선 그냥 유효기간을 무한대로 설정하면 됩니다. 엔터를 눌러 진행합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Key does not expire at all&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Is this correct? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GnuPG가 키가 유효기간이 없다고 경고하면 &lt;code&gt;y&lt;/code&gt;를 누르고 엔터로 무시합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GnuPG needs to construct a user ID to identify your key.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Real name:&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 GPG가 신상정보를 털어갑니다. 이름, 이메일, 그리고 키 코멘트를 입력하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Real name: Eric Park&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Email address: me@ericswpark.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Comment: https://ericswpark.com/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;You selected this USER-ID:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;저장하기 위해 &lt;code&gt;o&lt;/code&gt;를 누른 다음 엔터를 누릅니다. (만약 정보 변경이 필요하다면 괄호 안 글씨를 입력한 다음 엔터를 누르면 변경 절차가 시작됩니다!)&lt;/p&gt;
&lt;p&gt;이제 비밀번호를 입력하는 단계입니다. 입력 후 엔터를 누르는데, 이 과정을 한 번 더 반복합니다. 만약 누군가 이 키를 복사해간다면 이 비밀번호 없이는 키를 사용할 수 없습니다. (그래도 만약 키가 유출되었다면 &lt;a href=&quot;#%ED%82%A4-%ED%8F%90%EA%B8%B0%ED%95%98%EA%B8%B0&quot;&gt;키를 폐기하시는 것이 좋습니다.&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;비밀번호를 입력하면 GPG가 공용/개인 키 두 개를 만들기 시작합니다. 이 경고가 표시되는데:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;We need to generate a lot of random bytes. It is a good idea to perform&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;some other action (type on the keyboard, move the mouse, utilize the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;disks) during the prime generation; this gives the random number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;generator a better chance to gain enough entropy.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;컴퓨터가 오래되지 않았다면 이 과정은 약 30초 안에 완료됩니다. 터미널 프롬프트가 다음과 같이 바뀔 때까지 마우스 포인터를 몇 번 움직여줍니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: /Users/ericswpark/.gnupg/trustdb.gpg: trustdb created&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: key 56F399E7A57D6E5D marked as ultimately trusted&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: directory &apos;/Users/ericswpark/.gnupg/openpgp-revocs.d&apos; created&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: revocation certificate stored as &apos;/Users/ericswpark/.gnupg/openpgp-revocs.d/475C562EBC0520048AE79ADD56F399E7A57D6E5D.rev&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;public and secret key created and signed.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pub   rsa4096 2019-12-20 [SC]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    475C562EBC0520048AE79ADD56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;uid                      Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sub   rsa4096 2019-12-20 [E]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 첫 GPG 키가 생성되었습니다!&lt;/p&gt;
&lt;h1 id=&quot;사용-가능한-키-확인&quot;&gt;사용 가능한 키 확인&lt;/h1&gt;
&lt;p&gt;사용 가능한 키를 확인하려면 다음을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --list-keys&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 다음과 같은 출력이 표시되는데:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: checking the trustdb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: marginals needed: 3  completes needed: 1  trust model: pgp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/Users/ericswpark/.gnupg/pubring.kbx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;------------------------------------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pub   rsa4096/56F399E7A57D6E5D 2019-12-20 [SC]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    475C562EBC0520048AE79ADD56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;uid                 [ultimate] Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sub   rsa4096/2698CAAB8C3C9C8C 2019-12-20 [E]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기에서 몇몇 리눅스 배포판과 운영체제는 키 ID 전체를 보여주지 않을 수 있습니다. 정확한 출력을 보려면 다음을 입력하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --list-keys&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --keyid-format&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; long&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 키 개수가 작다면 전체 UUID를 입력하지 않기 위해 &lt;code&gt;short&lt;/code&gt;를 사용해도 됩니다.&lt;/p&gt;
&lt;h1 id=&quot;키-배포하기&quot;&gt;키 배포하기&lt;/h1&gt;
&lt;p&gt;키를 다른 분에게 공유하려면 먼저 키를 배포해야 합니다.&lt;/p&gt;
&lt;p&gt;일단 공용 키 ID를 찾아야 합니다. 먼저, &lt;a href=&quot;#%EC%82%AC%EC%9A%A9-%EA%B0%80%EB%8A%A5%ED%95%9C-%ED%82%A4-%ED%99%95%EC%9D%B8&quot;&gt;“사용 가능한 키 확인” 단계의 명령을 실행합니다.&lt;/a&gt; 출력에서 다음과 같은 줄을 찾습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pub   rsa4096/56F399E7A57D6E5D 2019-12-20 [SC]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;rsa4096/&lt;/code&gt; 다음에 오는 숫자가 다를텐데, 숫자 &lt;code&gt;56F399E7A57D6E5D&lt;/code&gt;가 있는 자리의 숫자가 공용 키 ID입니다.&lt;/p&gt;
&lt;p&gt;키 서버로 키를 배포합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --send-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 공용_키_ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예를 들어 다음과 같은 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --send-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;배포된-키-다운받기&quot;&gt;배포된 키 다운받기&lt;/h1&gt;
&lt;p&gt;만약 &lt;a href=&quot;#%ED%82%A4-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0&quot;&gt;위에 나와있는 명령으로 공용 키를 배포하셨다면&lt;/a&gt;, 다른 사람들이 공용 키를 다운받아서 정보를 암호화하거나 개인 키로 서명한 파일을 확인할 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;키를 블로그 같은 곳에 업로드하거나 (&lt;a href=&quot;https://ericswpark.com/.well-known/gpg.txt&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;힌트!&lt;/a&gt;) 키 서버에 업로드하면, 다른 사람들이 키를 받으려면 다음 명령을 실행하면 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --recv-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 공용_키_ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예를 들어, 사람들이 제 키를 받으려면 (여기에 나와 있는 키는 실제 사용하는 키가 아닙니다! 이 강좌글을 위해서만 만들어진 키라서, 실제 키는 제 &lt;a href=&quot;https://ericswpark.com/.well-known/gpg.txt&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;GPG 페이지에서&lt;/a&gt; 확인하실 수 있습니다), 다음을 실행하면 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --recv-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 GPG가 에러를 표시한다면 키가 배포되어 있지 않을 수도 있습니다. 먼저 키를 검색해보세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --search-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 공용_키_ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --search-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;다른-키-서버-사용하기&quot;&gt;다른 키 서버 사용하기&lt;/h1&gt;
&lt;p&gt;추가 플래그 없이 &lt;code&gt;gpg --send-keys&lt;/code&gt;나 &lt;code&gt;gpg --recv-keys&lt;/code&gt;를 실행하면 GnuPG는 기본 키 서버인 &lt;code&gt;hkps://hkps.pool.sks-keyservers.net&lt;/code&gt;을 사용합니다. 일반적인 URL과 달리, GnuPG는 &lt;code&gt;https&lt;/code&gt; 대신 &lt;code&gt;hkp&lt;/code&gt;와 &lt;code&gt;hkps&lt;/code&gt;를 사용합니다.&lt;/p&gt;
&lt;p&gt;다른 키 서버를 사용하는 예시를 들겠습니다. 예전에 Manjaro &lt;code&gt;.iso&lt;/code&gt;를 다운받아 검증할 때 프로젝트 리더인 Philip Müller의 키를 다른 키 서버에서 다운받아야 했습니다. Manjaro 위키에서도 나와있듯이, 이 분이 쓰는 키 서버 주소는 다음과 같습니다: &lt;code&gt;hkp://pool.sks-keyservers.net&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 키 서버 주소를 사용하려면, 다음 명령 형식의 플래그로 지정합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --keyserver&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; hkps://keyserver.url.here&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;기타&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 플래&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;그&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아까 예시로 돌아가서 Philip Müller의 키를 받아오려면, 다음을 실행해야 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --keyserver&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; hkp://pool.sks-keyservers.net&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --recv-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 11C7F07E&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때 키 서버 플래그 (&lt;code&gt;--keyserver&lt;/code&gt;)는 꼭 다른 플래그보다 먼저 입력해야 됩니다.&lt;/p&gt;
&lt;p&gt;2019년부로 기본 SKS 키 서버가 운영이 불안정한 것 같습니다. &lt;a href=&quot;https://code.firstlook.media/the-death-of-sks-pgp-keyservers-and-how-first-look-media-is-handling-it&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;여기에 First Look Media가 쓴 관련 글이 있는데 추가적인 정보는 여기에서 찾아보실 수 있습니다.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;위 글에서 추천하는 새로운 키 서버는 &lt;code&gt;keys.openpgp.org&lt;/code&gt; 키 서버인데, 이 서버는 &lt;a href=&quot;https://gitlab.com/hagrid-keyserver/hagrid/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Hagrid&lt;/a&gt;라는 새로운 백엔드를 사용해서 키를 배포한다고 하니 SKS 백엔드보다 보안 문제가 덜 하다고 합니다.&lt;/p&gt;
&lt;h1 id=&quot;폐기-인증서-만들기&quot;&gt;폐기 인증서 만들기&lt;/h1&gt;
&lt;p&gt;만약 개인 키가 유출되었을 경우 사용할 수 있는 폐기 인증서를 만들어야 합니다.&lt;/p&gt;
&lt;p&gt;다음 명령 형식을 사용해서 폐기 인증서를 발급합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --gen-revoke&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 공용_키_ID&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 공용_키_ID_폐기.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예를 들어, 제 개인 키에 연결된 폐기 인증서를 만드려면 다음 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --gen-revoke&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D_폐기.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 명령을 실행하면 다음과 같은 출력이 표시됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sec  rsa4096/56F399E7A57D6E5D 2019-12-20 Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Create a revocation certificate for this key? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;y&lt;/code&gt;와 엔터를 눌러 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please select the reason for the revocation:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    0 = No reason specified&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    1 = Key has been compromised&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    2 = Key is superseded&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    3 = Key is no longer used&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Q = Cancel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(Probably you want to select 1 here)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Your decision?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기에선 별다른 폐기 이유가 없다면, 엔터를 눌러 그냥 기본 옵션을 선택하시면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Enter an optional description; end it with an empty line:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 부분은 선택적이니 엔터를 눌러 그냥 바로 진행하시면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Reason for revocation: Key has been compromised&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Is this okay? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;y&lt;/code&gt;와 엔터를 눌러 확인합니다.&lt;/p&gt;
&lt;p&gt;GPG가 이제 폐기 인증서를 만들기 위해 개인 키 비밀번호를 물어봅니다. 비밀번호를 입력한 다음 엔터를 누르면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ASCII armored output forced.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Revocation certificate created.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please move it to a medium which you can hide away; if Mallory gets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;access to this certificate he can use it to make your key unusable.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;It is smart to print this certificate and store it away, just in case&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;your media become unreadable.  But have some caution:  The print system of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;your machine might store the data and make it available to others!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 GPG가 얘기하는 “Mallory”는 GPG 커뮤니티에서 “악의적 사용자들”을 가르키는 지칭입니다. &lt;del&gt;만약 이름이 Mallory라면 GPG 개발진을 고소하십시오&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;또 “ASCII armored output”이 뭔지 궁금하실텐데, 이 부분은 &lt;a href=&quot;#ascii-armored%EA%B0%80-%EB%AD%94%EA%B0%80%EC%9A%94&quot;&gt;일반 GPG 바이너리 파일과 ASCII-armored GPG 파일 차이점 부분에서 더 자세히 설명하겠습니다.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이제 폐기 인증서가 발급되었으면 안전한 곳에 저장해두시면 됩니다. 이 폐기 인증서만 있다면 키를 완전히 무효화시킬 수 있기에 주의하셔야 합니다. 어떤 분들은 이걸 프린트해서 보관하다가 필요하면 OCR로 인식해서 (…) 사용하시는 분들도 있는데, 그럴 필요까진 없다고 봅니다.&lt;/p&gt;
&lt;h1 id=&quot;키-폐기하기&quot;&gt;키 폐기하기&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;#%ED%8F%90%EA%B8%B0-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0&quot;&gt;위 섹션에서 만든 폐기 인증서를 불러온 다음&lt;/a&gt;, 일단 컴퓨터에 저장되어 있는 공용 키에 폐기 인증서를 묶어줍니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --import&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 공용_키_ID_폐기.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 명령 형식에 제 키를 대입하면:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --import&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D_폐기.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 폐기 인증서를 불러오신 다음, &lt;a href=&quot;#%ED%82%A4-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0&quot;&gt;“키 배포하기”&lt;/a&gt; 단계를 사용해서 폐기된 공용 키를 한 번 업로드 하셔야 합니다. 그래야 다른 분들이 폐기 표시가 된 공용 키를 다운받아 키가 더 이상 사용되지 않음을 알 수 있습니다. 이렇게 폐기된 공용 키를 한 번이라도 배포하시면 &lt;a href=&quot;#%ED%82%A4-%ED%8F%90%EA%B8%B0-%ED%95%B4%EC%A0%9C%ED%95%98%EA%B8%B0&quot;&gt;키 폐기 해제를 할 수 없습니다.&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;키-폐기-해제하기&quot;&gt;키 폐기 해제하기&lt;/h1&gt;
&lt;p&gt;만약 &lt;strong&gt;한 번이라도 폐기 처리된 공용 키를 배포하지 않으셨다면&lt;/strong&gt; 키 폐기를 취소하실 수 있습니다.&lt;/p&gt;
&lt;p&gt;폐기를 취소시키기 위해 GPG에게 공용 키를 삭제하라고 요청합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --expert&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --delete-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 공용_키_ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;제 키를 대입해 본다면:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --expert&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --delete-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--expert&lt;/code&gt; 플래그를 사용해서 공용 키&lt;strong&gt;만&lt;/strong&gt; 삭제를 하실 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg (GnuPG) 2.2.17; Copyright (C) 2019 Free Software Foundation, Inc.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;This is free software: you are free to change and redistribute it.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;There is NO WARRANTY, to the extent permitted by law.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pub  rsa4096/56F399E7A57D6E5D 2019-12-20 Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Delete this key from the keyring? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;y&lt;/code&gt;와 엔터를 눌러 확인합니다.&lt;/p&gt;
&lt;p&gt;이제 키 서버에서 다시 &lt;a href=&quot;#%EB%B0%B0%ED%8F%AC%EB%90%9C-%ED%82%A4-%EB%8B%A4%EC%9A%B4%EB%B0%9B%EA%B8%B0&quot;&gt;폐기 처리가 안 되어 있는 공용 키를 다운받거나&lt;/a&gt;, 아니면 복사본이 있으시다면 &lt;a href=&quot;#%ED%82%A4-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0&quot;&gt;복사본을 불러오기하셔도 됩니다.&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;키-불러오기&quot;&gt;키 불러오기&lt;/h1&gt;
&lt;p&gt;키를 로컬 저장소에서 불러오는 것도 중요합니다. 만약 키 서버를 사용하고 싶지 않으시거나 다른 분이 키를 바로 보내주시면 키를 불러오기해서 사용하실 수 있습니다.&lt;/p&gt;
&lt;p&gt;키를 불러올 때 파일 확장자가 &lt;code&gt;.gpg&lt;/code&gt;, &lt;code&gt;.asc&lt;/code&gt;, 또는 &lt;code&gt;.key&lt;/code&gt;인지 확인하시면 됩니다. 파일 확장자가 다른 이유는 &lt;code&gt;.asc&lt;/code&gt; 파일은 &lt;a href=&quot;#ascii-armored%EA%B0%80-%EB%AD%94%EA%B0%80%EC%9A%94&quot;&gt;ASCII-armored GPG 파일&lt;/a&gt;을 가르키기 때문입니다.&lt;/p&gt;
&lt;p&gt;다음 명령 형식을 사용하시면 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --import&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 키_파일_이름.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;ascii-armored가-뭔가요&quot;&gt;ASCII-armored가 뭔가요?&lt;/h1&gt;
&lt;p&gt;ASCII-armored는 파일이 바이너리 형식이 아니라 텍스트 방식의 파일로 출력되는 것을 의미합니다. 예를 들어, GPG로 암호화한 파일을 이메일로 보낼 때 유용하게 사용됩니다.&lt;/p&gt;
&lt;p&gt;ASCII-armored를 활성화하려면 &lt;code&gt;--armor&lt;/code&gt; 플래그를 명령에 추가하면 됩니다.&lt;/p&gt;
&lt;h1 id=&quot;키-서명하기&quot;&gt;키 서명하기&lt;/h1&gt;
&lt;p&gt;키를 관리하는 과정 중 하나가 바로 다른 사람들의 키를 서명하는 것입니다. 이 단계 다음에 어떻게 키를 활용하는지 설명드릴텐데, 이 섹션이 더 흥미로울 수도 있습니다.&lt;/p&gt;
&lt;p&gt;일단 &lt;a href=&quot;https://en.wikipedia.org/wiki/Web_of_trust&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;“신뢰망”&lt;/a&gt;을 이해하시는 것이 중요합니다. 이 모델과 비교되는 모델은 SSL이 사용하는 &lt;a href=&quot;https://en.wikipedia.org/wiki/Certificate_authority&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;“인증 기관”&lt;/a&gt; 모델입니다.&lt;/p&gt;
&lt;p&gt;브라우저에서 웹사이트에 접속할 때 보이는 초록색 자물쇠는 연결이 암호화되어 있고, 웹사이트의 주인이 검증되었다는 것을 나타냅니다. 이렇게 자물쇠를 표시하기 위해 웹사이트 주인은 Let’s Encrypt, Comodo, 또는 DigiCert 같은 인증 기관에서 SSL 인증서를 발급받아야 합니다. 이런 기관 중 SSL 인증서를 무료로 발급해주는 기관은 Let’s Encrypt 밖에 없는데, 이유는 기업과 다른 기관에서 기부를 많이 받고 있으며 인증서 유효기간이 짧고, 인증서 발급 절차가 자동화되어 있어 운영 비용이 적기 때문입니다.&lt;/p&gt;
&lt;p&gt;그럼 왜 다른 인증 기관은 SSL 인증서를 발급받는데 수수료를 청구할까요? SSL 인증서를 발급할 때마다 인증 기관은 실제 웹사이트 주인에게 인증서를 발급하는지 확인해야 합니다. 만약 잘못된 서버나 다른 분에게 인증서를 발급한다면 문제가 되고, &lt;a href=&quot;https://www.theregister.co.uk/2018/02/07/beware_the_coming_chrome_certificate_apocalypse/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;실제로 이전에 이렇게 인증서를 무단으로 발급받아 문제가 되었던 사건들이 많이 있습니다&lt;/a&gt;. 인증 기관마다 평판이 있는데, 이런 문제가 많이 생기면 평판이 내려가고, 평판이 너무 낮으면 브라우저 개발진이 인증 기관을 아예 브라우저 인증 데이터베이스에서 삭제해버리기에 인증 기관은 웹사이트 검증에 신경을 많이 써야 합니다.&lt;/p&gt;
&lt;p&gt;거기에다가, 어떤 정부는 사용자 컴퓨터에 자체 인증 기관의 설치를 강요해서 사용자들을 감시하는 경우도 있어, 이 모델은 보안이나 신뢰에 있어서 별로 좋지 않습니다. 한 인증 기관에서만 문제가 생겨도 거의 모든 사이트의 신뢰 구조가 무너지기 때문입니다.&lt;/p&gt;
&lt;p&gt;이런 문제점을 보완하기 위해서 GPG는 “신뢰망”이란 모델을 사용합니다. 제가 A란 친구가 있다고 가정해 봅시다. 전 A를 실제로 만났고 그가 누군지 알기에 그의 키를 서명해줍니다. 이제 A한테 B라는 친구가 있다고 가정하고, 저는 B가 누군지 모른다고 가정합니다. A는 똑같은 방식으로 B를 개인적으로 알기에 B의 키를 서명해줍니다. 나중에 서로 만나기 위해 정보를 주고받을 때, 제가 B가 보내주는 정보를 믿을 수 있는지 확인하고 싶다면 B의 웹사이트에 접속해서 B의 키를 다운받습니다. B의 키가 A의 키로 서명되어 있고, 제가 A의 키를 서명해주었기에 GPG는 이 새로운 키를 믿을 수 있다고 알려줍니다.&lt;/p&gt;
&lt;p&gt;이 모델은 사람들이 서로의 키를 많이 서명할수록 더 잘 작동합니다. 이렇게 관계도를 그래프로 나타내었을 때 시스템은 망과 같은 구조를 보입니다:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Web_of_Trust-en.svg/1200px-Web_of_Trust-en.svg.png&quot; alt=&quot;web-of-trust-diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C; 출처: &lt;a href=&quot;https://en.wikipedia.org/wiki/Web_of_trust&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Web_of_trust&lt;/a&gt; &gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Key_signing_party&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;거기에다 외국에선 키 서명 파티라는 것도 있습니다!&lt;/a&gt; (국내에서도 이런 게 있는지는 모르겠습니다.) 물론, 키를 서명할 때 꼭 상대방의 정보를 확인해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://imgs.xkcd.com/comics/responsible_behavior.png&quot; alt=&quot;xkcd-responsible-behavior&quot;&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C; 출처: &lt;a href=&quot;https://xkcd.com/364/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://xkcd.com/364/&lt;/a&gt; &gt;&lt;/p&gt;
&lt;p&gt;과정은 간단합니다. 공용 키 아이디를 메모해 둔 다음에 (키 서명 파티에 실제 키 파일들을 들고 가는 것은 개인 키 유출의 위험이 있기에 가지고 가지 않습니다), 키 서명 파티에 참석해 만나는 사람들의 명의를 확인한 다음 상대방의 공용 키 ID를 모두 메모해 둡니다. 그 다음, 집에 와서 &lt;a href=&quot;#%EB%B0%B0%ED%8F%AC%EB%90%9C-%ED%82%A4-%EB%8B%A4%EC%9A%B4%EB%B0%9B%EA%B8%B0&quot;&gt;적어둔 공용 키들을 다운받고&lt;/a&gt;, 다음 형식으로 서명하시면 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --sign-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 상대방_공용_키_ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서명을 한 다음에 다시 서명된 &lt;strong&gt;상대방의 공용 키&lt;/strong&gt;를 &lt;a href=&quot;#%ED%82%A4-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0&quot;&gt;배포해야 합니다.&lt;/a&gt; 이때, 만약 누군가 키를 배포하지 않고 직접 보내줬다면 배포를 원치 않을 수도 있기에 꼭 확인하시고 배포하세요.&lt;/p&gt;
&lt;p&gt;예시로, 아까의 Manjaro 개발자의 키를 서명하려면:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --sign-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 11C7F07E&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다음과 같은 출력이 표시됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pub  rsa2048/CAA6A59611C7F07E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2012-05-05  expires: never       usage: SC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    trust: unknown       validity: unknown&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sub  rsa2048/320011450576724A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2012-05-05  expires: never       usage: E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[ unknown] (1). Philip Müller (Called Little) &amp;#x3C;philm@manjaro.org&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pub  rsa2048/CAA6A59611C7F07E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2012-05-05  expires: never       usage: SC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    trust: unknown       validity: unknown&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Primary key fingerprint: E4CD FE50 A2DA 85D5 8C8A  8C70 CAA6 A596 11C7 F07E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Philip Müller (Called Little) &amp;#x3C;philm@manjaro.org&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Are you sure that you want to sign this key with your&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;key &quot;Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&quot; (56F399E7A57D6E5D)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Really sign? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;y&lt;/code&gt;를 누르고 엔터로 확인합니다. GPG가 비밀번호를 물어보면 입력한 다음 엔터를 눌러 키를 서명합니다.&lt;/p&gt;
&lt;p&gt;밑에서 더 서술하겠지만 이렇게 서명을 한 다음 키의 &lt;a href=&quot;#%EC%8B%A0%EB%A2%B0-%EC%88%98%EC%A4%80%EC%9D%B4%EB%9E%80&quot;&gt;“신뢰 수준”&lt;/a&gt;을 업데이트해야 할 수도 있습니다.&lt;/p&gt;
&lt;h1 id=&quot;키-서명하기-파티-가이드&quot;&gt;키 서명하기 파티 가이드&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;키 서명 파티에는 컴퓨터를 가져가지 않습니다. 바이러스나 컴퓨터 자체의 분실로, 개인 키가 유출될 가능성이 높기 때문입니다. 따라서 공용 키를 배포한 다음 ID를 적어가서 공유하는 것이 좋습니다.&lt;/li&gt;
&lt;li&gt;서명하는 키가 친구나 지인의 키라면 그냥 바로 서명해도 되지만, 만약 상대방이 아는 사람이 아니라면 여권이나 기타 신분증을 가지고 신분을 꼭 확인하고 서명해야 합니다.&lt;/li&gt;
&lt;li&gt;서명하는 과정은 집에서 한꺼번에 해도 됩니다. 특이하게도, “키 서명 파티”에서는 키 서명을 하지 않고, 그냥 정보 확인과 ID 공유만 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;신뢰-수준이란&quot;&gt;신뢰 수준이란?&lt;/h1&gt;
&lt;p&gt;GnuPG 키 저장소에서 다른 사람이 얼마나 믿을만한 사람인지 적어두는 것이 바로 GPG의 “신뢰 수준” 설정입니다.&lt;/p&gt;
&lt;p&gt;또 다시 예시를 들겠습니다. 만약 친구가 컴퓨터를 진짜 잘 사용하는 친구라고 가정하면, 이 친구가 다른 사람의 키를 서명할 때 좋은 판단을 내릴 거라고 확신할 수 있기에 이 친구에겐 “신뢰 수준”을 높게 설정합니다. 이와는 반대로 아까의 키 서명하기 파티에서 모르는 사람을 만났다고 가정합니다. 이 사람은 키를 잘 서명하는지 믿을 수 없기에 “신뢰 수준”을 낮은 단계인 1로 설정합니다.&lt;/p&gt;
&lt;p&gt;이때 새로운 키를 서명하면 그 키는 자동으로 1 (모름) 단계로 설정됩니다. 만약 “신뢰 수준”을 판단하기 어려우시다면 그냥 기본값으로 놓으셔도 괜찮습니다.&lt;/p&gt;
&lt;p&gt;키의 “신뢰 수준”을 변경하려면 다음 명령 형식을 사용합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --edit-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 타인_공용_키_ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그 다음 &lt;code&gt;trust&lt;/code&gt;와 엔터를 입력해서 “신뢰 수준”을 지정한 다음 엔터를 눌러 설정값을 적용합니다. “신뢰 수준” 변경사항은 바로 저장되기에 별다른 저장 절차 없이 바로 &lt;code&gt;quit&lt;/code&gt;와 엔터를 눌러 GPG 프롬프트에서 빠져나옵니다.&lt;/p&gt;
&lt;h1 id=&quot;키-백업하기&quot;&gt;키 백업하기&lt;/h1&gt;
&lt;p&gt;컴퓨터가 망가지거나, USB가 고장나거나, 어떤 방식으로든 키는 잃어버릴 수 있어 꼭 백업을 해 두는 것이 중요합니다.&lt;/p&gt;
&lt;p&gt;가장 편한 백업은 &lt;code&gt;.gnupg&lt;/code&gt; 폴더 전체를 복사해두는 것입니다. 근데, 이렇게 백업을 진행하시면 GPG의 잔여 파일이 백업에 포함되어 크기가 쓸데없이 커집니다. 키만 백업하는 방법을 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;일단 개인 키를 백업하겠습니다. 다음 형식을 편집해서 실행하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --export-secret-keys&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --armor&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 키_ID&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 키_ID_개인.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 실행하면 키가  &lt;code&gt;키_ID_개인.asc&lt;/code&gt;에 저장됩니다. 이 파일을 안전한 곳에 저장하시면 됩니다.&lt;/p&gt;
&lt;p&gt;나중에 개인 키에서 공용 키를 다시 발급하는 절차가 꽤 귀찮기에 이번 기회에 공용 키도 같이 백업해둡니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --export&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --armor&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 키_ID&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 키_ID_공용.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 나중에 백업을 사용해서 키를 복원할 경우, &lt;a href=&quot;#what-is-trust&quot;&gt;키의 “신뢰 수준”을 5로 설정해야&lt;/a&gt; GPG가 키가 개인 키임을 알 수 있습니다.&lt;/p&gt;
&lt;h1 id=&quot;서브키-사용법&quot;&gt;서브키 사용법&lt;/h1&gt;
&lt;p&gt;서브키는 키를 원본 “마스터” 키에서 본뜬 것이라고 생각하시면 됩니다. 일반 마스터 키와 다르게, 유출이 되더라도 폐기 절차가 복잡하지 않기에 서브키 사용을 추천합니다.&lt;/p&gt;
&lt;p&gt;일단 서브키를 만들기 위해선 마스터 키를 편집해야 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --edit-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 키_ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 다음과 같은 출력이 표시됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg (GnuPG) 2.2.17; Copyright (C) 2019 Free Software Foundation, Inc.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;This is free software: you are free to change and redistribute it.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;There is NO WARRANTY, to the extent permitted by law.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Secret key is available.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sec  rsa4096/56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2019-12-20  expires: never       usage: SC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    trust: ultimate      validity: ultimate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ssb  rsa4096/2698CAAB8C3C9C8C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2019-12-20  expires: never       usage: E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[ultimate] (1). Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서브키를 &lt;code&gt;addkey&lt;/code&gt;로 추가하겠습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please select what kind of key you want:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (3) DSA (sign only)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (4) RSA (sign only)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (5) Elgamal (encrypt only)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (6) RSA (encrypt only)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Your selection?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서브키는 두 개 만드시는 것이 좋습니다. 한 키는 서명을 할 때 사용하고, 한 키는 암호화/복호화 작업에 사용하면 됩니다. 마스터 키를 만들 때 암호화 서브키가 자동으로 만들어지기에 서명용 서브키를 만들어보겠습니다. &lt;code&gt;4&lt;/code&gt;를 누른 후 엔터를 누릅니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RSA keys may be between 1024 and 4096 bits long.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;What keysize do you want? (2048)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 아까와 같이 &lt;code&gt;4096&lt;/code&gt;을 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Requested keysize is 4096 bits&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please specify how long the key should be valid.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        0 = key does not expire&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;  = key expires in n days&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;w = key expires in n weeks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;m = key expires in n months&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;y = key expires in n years&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Key is valid for? (0)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아까 말했듯이 서브키는 폐기가 용이하기에 그냥 만료되지 않는 키를 만들어도 괜찮습니다. 만약, 예시로 3개월 이후에 만료되는 서브키를 만들려면 여기에서 &lt;code&gt;3m&lt;/code&gt;을 입력하면 됩니다. 일단 기본 설정으로 진행하기 위해 엔터를 누릅니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Key does not expire at all&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Is this correct? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;y&lt;/code&gt;를 누르고 엔터로 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Really create? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;y&lt;/code&gt;를 누르고 엔터로 확인합니다. 비밀번호를 물어본 다음, 엔트로피 경고를 띄운 후 서브키를 만들어줍니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sec  rsa4096/56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2019-12-20  expires: never       usage: SC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    trust: ultimate      validity: ultimate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ssb  rsa4096/2698CAAB8C3C9C8C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2019-12-20  expires: never       usage: E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ssb  rsa4096/CFE50DE6144F5CA0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2019-12-20  expires: never       usage: S&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[ultimate] (1). Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기에서 키 ID &lt;code&gt;CFE50DE6144F5CA0&lt;/code&gt;가 새로 만들어진 서브키입니다! 이제 키의 변경사항을 모두 저장해야 됩니다. &lt;code&gt;save&lt;/code&gt;와 엔터를 눌러 저장합니다.&lt;/p&gt;
&lt;p&gt;이제 서브키를 내보내기해서 다른 곳에서 사용할 준비가 완료되었습니다. 다음 형식과 같이 입력합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --export-secret-subkeys&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --armor&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CFE50DE6144F5CA0!&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CFE50DE6144F5CA0_서브키.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때, 키 ID 뒤에 붙는 느낌표가 &lt;strong&gt;매우 중요합니다.&lt;/strong&gt; 만약 느낌표를 붙이지 않을 경우 모든 서브키를 내보내기에 실수로 보안이 취약한 컴퓨터에 암호화/복호화 키를 보낼 수가 있어서 각별한 주의가 필요합니다.&lt;/p&gt;
&lt;p&gt;그런 다음 키를 가져올 컴퓨터에서 다음 형식에 맞춰 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --import&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CFE50DE6144F5CA0_서브키.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 키를 폐기하고 싶으시다면 그냥 마스터 키를 편집해서 &lt;code&gt;revkey&lt;/code&gt;를 사용해 서브키를 폐기한 다음, 변경사항을 저장 후 키 서버에 업로드를 하면 됩니다.&lt;/p&gt;
&lt;p&gt;만약 키 전체를 삭제하고 싶으시다면, 다음을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --delete-secret-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 키_ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 명령은 키 저장소도 한꺼번에 삭제하기에 조심해서 사용하세요.&lt;/p&gt;
&lt;h1 id=&quot;키-업데이트하기&quot;&gt;키 업데이트하기&lt;/h1&gt;
&lt;p&gt;사람들이 키를 수시로 서명하거나, 폐기하거나, 새로 발급하기에, 키 저장소를 수시로 업데이트하는 것이 중요합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --refresh-keys&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 명령은 배포된 공용 키도 업데이트해줍니다.&lt;/p&gt;
&lt;p&gt;이제 본격적으로 GPG 키를 어떻게 활용하는지 알려드리겠습니다!&lt;/p&gt;
&lt;h1 id=&quot;파일-암호화하기&quot;&gt;파일 암호화하기&lt;/h1&gt;
&lt;p&gt;상대방의 공용 키를 가져온 다음, 다음 명령 형식으로 암호화할 파일의 전체 경로를 입력합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --encrypt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 파일.tar.gz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GPG가 무슨 키 ID로 암호화할지 묻는다면, 키 저장소에 키가 이미 불러오기되어 있다면 상대방의 이름을 적거나 상대방의 키 ID를 적으시면 됩니다.&lt;/p&gt;
&lt;p&gt;아니면 명령 자체에 덧붙이셔도 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --encrypt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 파일.tar.gz&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -r&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;박선우&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때 출력물을 &lt;a href=&quot;#ascii-armored%EA%B0%80-%EB%AD%94%EA%B0%80%EC%9A%94&quot;&gt;ASCII-armor&lt;/a&gt;화 하실 수 있습니다!&lt;/p&gt;
&lt;h1 id=&quot;파일-복호화하기&quot;&gt;파일 복호화하기&lt;/h1&gt;
&lt;p&gt;다음 명령 형식을 사용합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --decrypt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 피일.tar.gz.gpg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 파일이 &lt;a href=&quot;#ascii-armored%EA%B0%80-%EB%AD%94%EA%B0%80%EC%9A%94&quot;&gt;ASCII-armor&lt;/a&gt; 형식이라면 &lt;code&gt;.asc&lt;/code&gt;를 사용합니다.&lt;/p&gt;
&lt;h1 id=&quot;파일-서명하기&quot;&gt;파일 서명하기&lt;/h1&gt;
&lt;p&gt;파일을 어떻게 서명할지에 따라서 명령 형식에 차이가 있습니다. 만약 압축되어 있는 서명된 파일을 생성하고 싶으시다면 다음 명령을 사용합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --sign&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 파일.tar.gz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 명령은 &lt;code&gt;--armor&lt;/code&gt; 플래그의 사용에 따라서 &lt;code&gt;파일.tar.gz.gpg&lt;/code&gt; 혹은 &lt;code&gt;파일.tar.gz.asc&lt;/code&gt;을 생성합니다.&lt;/p&gt;
&lt;p&gt;이제 파일을 전송할 때 &lt;code&gt;file.tar.gz.asc&lt;/code&gt; 파일 하나만 전송하시면 됩니다. 상대방이 파일을 받으면 같은 파일로 서명을 확인하거나 원본 파일을 압축해제할 수 있습니다.&lt;/p&gt;
&lt;p&gt;그런데, 만약 이메일처럼 별도의 압축해제 과정 없이 정보를 서명해야 되는 상황이라면 &lt;code&gt;--clearsign&lt;/code&gt;이라는 구문을 사용해서 텍스트-방식의 서명을 사용하실 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --clearsign&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 파일.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 방식은 이메일이나 텍스트 문서를 보낼 때 유용하게 사용됩니다. 텍스트가 먼저 표시된 다음, 바로 밑에 서명 정보가 덧대어져 전송됩니다.&lt;/p&gt;
&lt;p&gt;이제 마지막 방식은 큰 파일에 사용됩니다. 위의 두 방식은 서명 파일이 원본 파일의 크기만큼을 차지하는데, 큰 파일은 이중으로 공간을 차지하기에 이렇게 서명하는 것이 불편하실 수 있습니다. 따라서, 서명 파일을 별도로 분리하는 모드를 사용하시면 GPG는 서명을 계산한 다음 별도의 서명 파일에 저장합니다. 거의 대부분의 리눅스 배포판이나 큰 라이브러리/프레임워크 프로젝트가 이런 방식으로 서명 파일을 배포합니다. 이 모드로 서명 파일을 생성하려면 다음 명령을 사용합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --detach-sig&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 파일.iso&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼, 이 명령과 첫 명령의 차이점을 알아보겠습니다. 첫 명령은 파일 전체를 압축한 다음, 서명 정보를 추가 한 다음 새로운 파일을 생성합니다. 이 파일 하나만으로 모든 정보를 전송할 수 있기에 파일 여러 개를 보낼 수 없는 상황에서는 유용하지만, 이렇게 파일을 압축하고 서명하는 과정, 그리고 다시 압축된 파일을 압축해제하는 과정이 리소스를 많이 사용하기 떄문에 파일 크기에 제약이 있습니다.&lt;/p&gt;
&lt;p&gt;하지만, “detach” 방식으로 서명 파일을 생성하면 실제 서명 파일은 크기가 매우 작습니다. 이 파일과 원본 파일을 동일 경로에 저장한 다음 GPG를 사용해서 원본 파일의 서명을 확인하실 수 있습니다. 리눅스 배포판 다운로드 섹션에 있는 “Ubuntu.iso”, “Ubuntu.iso.sig”, 또는 “Ubuntu.iso.asc” 파일들이 바로 이 “detach” 방식으로 생성된 서명 파일과 원본 파일을 업로드해 둔 것입니다.&lt;/p&gt;
&lt;p&gt;만약 첫 번째 방식의 파일을 다운받아 서명을 확인하면 GPG는 파일이 분리되어 있는 서명이 아니라고 경고합니다. 따라서 이렇게 통합되어 있는 파일이 손상될 경우 GPG는 손상이 발생했는지 확인을 하지 못 할 수도 있습니다. 이렇게 통합되어 있는 파일을 확인하기 위해서는 파일을 압축해제 한 다음, 체크섬을 이용해서 파일을 검증해야 합니다. 따라서 첫 번째 방식은 작은 파일을 서명할 때만 사용하시는 것이 좋습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;요약&lt;/strong&gt; - 이메일이나 텍스트 서명은 &lt;code&gt;--clearsign&lt;/code&gt;, 큰 파일은 “detach” 방식, 그리고 일반적인 작은 파일(대략 수십 MB 정도)은 압축 및 서명을 사용하시면 됩니다.&lt;/p&gt;
&lt;h1 id=&quot;파일-서명-확인하기&quot;&gt;파일 서명 확인하기&lt;/h1&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --verify&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 파일.tar.gz.gpg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;끝&quot;&gt;끝&lt;/h1&gt;
&lt;p&gt;긴 글 읽어주셔서 감사합니다! GPG를 사용하는데 이 글이 도움이 되었으면 합니다.&lt;/p&gt;</content:encoded></item><item><title>UnRAID에 Backblaze B2로 백업하는 Duplicacy 설치하기</title><link>https://ericswpark.com/ko/blog/2019/2019-11-10-setting-up-duplicacy-on-unraid-with-backblaze-b2/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2019/2019-11-10-setting-up-duplicacy-on-unraid-with-backblaze-b2/</guid><pubDate>Sun, 10 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이 글에선 Duplicacy라는 만능 데이터 백업 툴을 알아보겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;duplicacy가-뭔가요&quot;&gt;Duplicacy가 뭔가요?&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://duplicacy.com/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Duplicacy&lt;/a&gt;는 서버를 위한 백업/복원 툴입니다. CLI 버전과 GUI 버전이 있는데, 터미널을 사용하실 수 있다면 무료 버전으로도 충분합니다.&lt;/p&gt;
&lt;h1 id=&quot;경고&quot;&gt;경고&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Duplicacy는 백업에 단일 연결만을 지원합니다. 만약 한 머신에서 백업을 진행하고 있는데 다른 머신에서 &lt;code&gt;prune&lt;/code&gt; 명령을 실행한다면 &lt;strong&gt;정보가 모두 유실될 수 있으니&lt;/strong&gt; 주의하셔야 합니다. &lt;strong&gt;한 번에 한 개의 Duplicacy 인스턴스만을 돌려야 합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;RAID는 백업이 아닙니다. 만약 RAID가 백업이라고 하는 분이 있다면 멍청하다고 알려 주세요.&lt;/li&gt;
&lt;li&gt;복원을 할 수 없으면 백업이 아닙니다. Amazon Glacier 같은 서비스를 사용해서 데이터를 다 백업했는데 집이 화재로 몽땅 타 버린다면 집 구하기에 신경을 쓰지, Glacier에서 복원은 정신나간 가격 때문에 상상도 못 합니다. 언제나 쉽게 접근할 수 있는 저장소에 백업하고 어쩌다가 한 번씩 백업을 테스트해서 나중에 복원 시 문제가 없도록 해야 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그럼 가이드를 진행하겠습니다!&lt;/p&gt;
&lt;h1 id=&quot;설치-방법&quot;&gt;설치 방법&lt;/h1&gt;
&lt;p&gt;UnRAID 시스템은 USB 메모리에서 부팅해서 램으로 복사되기에, 시스템 폴더에 그냥 Duplicacy 실행 파일을 복사한다면 서버가 재시작되면 파일이 사라지게 됩니다. 따라서 USB 메모리 자체에 먼저 Duplicacy를 설치한 다음 매번 부팅 때마다 실행 파일을 링크해 오는 것이 중요합니다.&lt;/p&gt;
&lt;p&gt;UnRAID 서버의 USB 저장소로 이동합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /boot/config/plugins&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기에 Duplicacy 실행 파일을 다운받습니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;wget&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -O&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; duplicacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://github.com/gilbertchen/duplicacy/releases/download/v2.2.3/duplicacy_linux_x64_2.2.3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아마도 이걸 읽으실 쯤엔 Duplicacy의 새 버전이 나와있을 테니, Duplicacy의 &lt;a href=&quot;https://github.com/gilbertchen/duplicacy/releases&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;깃허브 릴리스 탭으로 가셔서&lt;/a&gt;, &lt;code&gt;duplicacy_linux_x64_xxx&lt;/code&gt; (여기에서 &lt;code&gt;xxx&lt;/code&gt;는 버전을 가르킵니다)를 우클릭한 다음 링크 주소를 복사해서 &lt;code&gt;wget -O duplicacy&lt;/code&gt; 다음에 붙여넣으면 됩니다.&lt;/p&gt;
&lt;p&gt;그 다음으로 &lt;code&gt;go&lt;/code&gt; 파일을 수정해서 실행 파일을 매번 부팅 시 링크하도록 하겠습니다. &lt;code&gt;/boot/config/go&lt;/code&gt;를 연 다음 파일 끝에 다음 두 줄을 추가해 주세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Duplicacy 링크&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ln&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /boot/config/plugins/duplicacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /usr/local/bin/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 두 줄을 추가하면 매번 부팅마다 Duplicacy 실행 파일이 시스템 디렉터리로 링크됩니다.&lt;/p&gt;
&lt;p&gt;이제 서버를 재시작하거나 &lt;code&gt;ln -s /boot/config/plugins/duplicacy /usr/local/bin/&lt;/code&gt;를 한 번 실행하신 다음 &lt;code&gt;duplicacy -h&lt;/code&gt;를 입력해서 터미널에 Duplicacy가 설치되었는지 확인하세요.&lt;/p&gt;
&lt;h1 id=&quot;duplicacy-repository-만들기&quot;&gt;Duplicacy “repository” 만들기&lt;/h1&gt;
&lt;p&gt;이제 Duplicacy로 “repository” (저장소)를 &lt;code&gt;init&lt;/code&gt;으로 만들겠습니다. 이렇게 저장소를 생성하면 현재 경로에 있는 모든 파일을 백업 대상으로 지정하게 됩니다. 이렇게 지정된 저장소 경로에 &lt;code&gt;.duplicacy&lt;/code&gt;라는 새로운 숨겨진 폴더가 하나 추가되고, Duplicacy는 여기에 저장소 정보를 저장합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;duplicacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; init&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 저장소_이름&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2://서버_이름-duplicacy&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -e&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;명령 설명은 다음과 같습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;현재 있는 경로가 &lt;code&gt;/mnt/user&lt;/code&gt; 라면 이 경로 아래 있는 모든 파일과 폴더들이 백업됩니다. 예를 들어서, Duplicacy는 &lt;code&gt;/mnt/user/Movies&lt;/code&gt;, &lt;code&gt;/mnt/user/Music&lt;/code&gt; 같은 폴더와 파일을 모두 백업합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;init&lt;/code&gt;은 Duplicacy에게 “여기에 저장소를 만들어줘”라고 알려줍니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;저장소_이름&lt;/code&gt;은 그냥 저장소 이름입니다. 아무렇게나 적으셔도 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;b2://&lt;/code&gt;는 프로토콜 정보입니다. 여기에서 쓰는 프로토콜은 Backblaze B2입니다. 만약 다른 백업 서버 제공업체를 사용하신다면 알맞게 다른 설정을 하시면 됩니다 (예를 들어, &lt;code&gt;sftp://&lt;/code&gt;, 등등).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;서버_이름-duplicacy&lt;/code&gt;는 B2 서버 상에 파일을 저장할 “bucket”입니다. 이 명령을 실행하기 전에 만드셔야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-e&lt;/code&gt;는 클라이언트-암호화를 활성화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;UnRAID에선 share 폴더 루트(&lt;code&gt;/mnt/user/*&lt;/code&gt;)에다가 저장소를 생성할 수 있습니다. 이렿게 생성하시게 된다면 &lt;code&gt;duplicacy&lt;/code&gt;는 모든 저장소 정보와 설정을 &lt;code&gt;/mnt/user/.duplicacy/*&lt;/code&gt;에 저장합니다.&lt;/p&gt;
&lt;h1 id=&quot;duplicacy-키-저장해두기&quot;&gt;Duplicacy 키 저장해두기&lt;/h1&gt;
&lt;p&gt;이제 본격적으로 이 프로세스를 자동화하려면 Duplicacy가 매번 어플리케이션 ID와 비밀번호를 물어보지 않게 키를 저장해두는 것입니다. 아니면 매번 백업할 때마다 키를 제공해야 되는데, 그럼 백업이 귀찮아져서 잘 하지 않게 되기에 그냥 자동화하는 것을 추천합니다.&lt;/p&gt;
&lt;p&gt;여기에서 주의할 점은, 이 명령들을 실행할 경우 &lt;strong&gt;어플리케이션 키와 암호화 비밀번호가 평문으로 &lt;code&gt;.duplicacy&lt;/code&gt; 폴더에 저장됩니다. 이 폴더에 접근할 수 있다면 키를 볼 수 있기에&lt;/strong&gt;, 서버 보안과 관리에 신경을 쓸 필요가 있습니다.&lt;/p&gt;
&lt;p&gt;다음 명령들을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;duplicacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; set&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -storage&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2://서버_이름-duplicacy&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2_id&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -value&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2_아이디&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;duplicacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; set&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -storage&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2://서버_이름-duplicacy&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2_key&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -value&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2_키&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;duplicacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; set&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -storage&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2://서버_이름-duplicacy&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; password&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -value&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 암호화-키&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;폴더파일-제외하기&quot;&gt;폴더/파일 제외하기&lt;/h1&gt;
&lt;p&gt;다음으로 백업을 실행하기 전, 특정 폴더나 파일을 제외하고 싶다면 &lt;code&gt;.duplicacy/&lt;/code&gt; 폴더 안 필터를 조절해야 합니다. 저는 여기에서 손쉽게 재생성할 수 있는 Docker 볼륨 폴더, VM 폴더, 그리고 Docker 이미지를 제외하도록 설정했습니다.&lt;/p&gt;
&lt;p&gt;자세한 사항은 &lt;a href=&quot;https://forum.duplicacy.com/t/filters-include-exclude-patterns/1089&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;공식 설명서에서 확인하시면 됩니다.&lt;/a&gt; 정규식 표현이 많은데 전 잘 몰라서 UnRAID의 &lt;code&gt;filters&lt;/code&gt; 파일을 그냥 다음과 같이 설정했습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-appdata/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-backup/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-VM/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-Downloads/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-ISO/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-docker.img&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-libvrt.img&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;백업-자동화하기&quot;&gt;백업 자동화하기&lt;/h1&gt;
&lt;p&gt;이제 마지막으로 “User Scripts”를 사용해서 백업을 자동화해야 됩니다. 이렇게 자동화를 하면 매번 서버에 로그인해서 백업 명령을 실행할 필요가 없습니다.&lt;/p&gt;
&lt;p&gt;“User Scripts”를 다운받아서 UnRAID 서버에 설치합니다 (온라인에 튜토리얼이 많으니 여기에선 자세하게 다루진 않겠습니다). 설치가 완료되면 다음 스크립트를 만듭니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# https://stackoverflow.com/a/185473/1388019&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;lockfile&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/tmp/duplicacy.lock&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-e&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ${lockfile} ] &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;kill&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;cat&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;lockfile&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    echo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;duplicacy 이미 실행중&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    exit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 종료 시 삭제되는 lockfile 생성하기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;trap&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;rm -f ${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;lockfile&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}; exit&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; INT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; TERM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; $$&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ${lockfile}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 기본 설정으로 백업 수행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /mnt/user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/usr/local/bin/duplicacy&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -log&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -threads&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -stats&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# lockfile 삭제&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;rm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -f&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ${lockfile}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;스크립트를 간단히 설명하자면, 처음 몇 줄에선 (&lt;code&gt;lockfile&lt;/code&gt;부터 시작해서 &lt;code&gt;echo $$&lt;/code&gt;까지) lockfile이란 파일을 생성합니다. 만약 “User Scripts”에서 스크립트를 여러 번 실행할 경우 Duplicacy가 백업을 손상시키지 않게 먼저 lockfile의 존재를 확인한 다음, 파일이 이미 존재하면 스크립트 동작을 중단합니다.&lt;/p&gt;
&lt;p&gt;그 다음 두 줄은 백업을 2개의 쓰레드로 실행합니다. CPU의 쓰레드 수에 맞게 조정하시면 됩니다. &lt;code&gt;-stats&lt;/code&gt; 플래그는 무슨 파일을 백업하는지 로그에 기록합니다.&lt;/p&gt;
&lt;p&gt;이제 이 스크립트를 알맞는 제목과 설명을 적어 저장한 다음 스케줄을 선택하시면 됩니다. 그럼 백업이 자동으로 설정된 스케줄에 맞춰서 진행됩니다.&lt;/p&gt;
&lt;h1 id=&quot;낡은-백업-삭제하기&quot;&gt;낡은 백업 삭제하기&lt;/h1&gt;
&lt;p&gt;Duplicacy는 자동으로 낡은 백업을 삭제하진 않는데, 낡은 백업을 삭제하기 위해 &lt;code&gt;prune&lt;/code&gt;이라는 명령을 사용하겠습니다.&lt;/p&gt;
&lt;p&gt;다음 두 줄을 원래 스크립트에 추가합니다 (“lockfile 삭제” 부분 위에 추가하시면 됩니다):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 120일보다 오래된 백업 삭제하기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;duplicacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; prune&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -exhaustive&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -exclusive&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -keep&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 0:120&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 줄은 120일보다 오래된 백업을 삭제하는데, 날짜를 알맞게 조정하시면 됩니다.&lt;/p&gt;
&lt;h1 id=&quot;복원은-어떻게-하나요&quot;&gt;복원은 어떻게 하나요?&lt;/h1&gt;
&lt;p&gt;서버에서 데이터를 아직까지 잃은 적이 없어서 이 부분은 못 적었습니다. 만약 복원해야 된다면 이 섹션을 업데이트하겠습니다.&lt;/p&gt;
&lt;p&gt;근데, 절차가 간단할 것으로 예상합니다. 새로운 서버에 설정 파일을 복원한 후, &lt;code&gt;duplicacy restore&lt;/code&gt; 명령을 사용해서 복원하시면 되겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;제거하기&quot;&gt;제거하기&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;/boot/config/plugins/duplicacy&lt;/code&gt; 실행 파일을 제거합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/boot/config/go&lt;/code&gt; 파일을 수정해서 추가했던 두 줄을 삭제합니다.&lt;/li&gt;
&lt;li&gt;필요하다면 설정 파일을 삭제합니다. 소스 폴더에 들어가서 &lt;code&gt;.duplicacy&lt;/code&gt; 폴더를 삭제하시면 됩니다.&lt;/li&gt;
&lt;li&gt;“User Scripts” 플러그인을 삭제합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그 다음 UnRAID 서버를 재시작하시면 됩니다.&lt;/p&gt;</content:encoded></item><item><title>맥의 CPU 모델 확인하기</title><link>https://ericswpark.com/ko/blog/2019/2019-07-29-find-your-cpu-model-on-macos/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2019/2019-07-29-find-your-cpu-model-on-macos/</guid><pubDate>Mon, 29 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;애플 웹사이트에서 맥을 구매할 때, 대부분의 페이지에서 “인텔의 몇번째 세대 CPU를 탑재,” “속도는 터보해서 3.2GHz”와 같은 홍보 문구가 나오지만, 정작 정확한 CPU 모델명은 표기하지 않아 확인하기 힘듭니다. 스펙 페이지에서도 모델명만 이상하게 빠져 있는데, 왜 굳이 애플이 이걸 숨기는지 모르겠습니다.&lt;/p&gt;
&lt;p&gt;만약 맥의 CPU 모델명을 확인하고 싶다면 다음 커맨드를 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sysctl&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; machdep.cpu.brand_string&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 예를 들어 다음과 같은 모델명이 표시됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그냥 스펙시트에서 보여달라고요…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;수정 (2020)&lt;/strong&gt;: 아니 모델명을 보여달라고 했지 아예 CPU 업체를 갈아?! 참 애플다운 솔루션이네요. 뭐 그래도 예전 인텔 기반 맥의 모델명을 확인하는데 유용해서 이 글을 남겨둡니다.&lt;/p&gt;</content:encoded></item><item><title>리버스 SSH 터널로 NAT 우회하기</title><link>https://ericswpark.com/ko/blog/2018/2018-08-20-bypassing-nat-with-reverse-ssh-tunneling/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2018/2018-08-20-bypassing-nat-with-reverse-ssh-tunneling/</guid><pubDate>Mon, 20 Aug 2018 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;중국-인터넷-문제&quot;&gt;중국 인터넷 문제&lt;/h1&gt;
&lt;p&gt;중국으로 이사하면서 홈 서버와 네트워크가 정상적으로 동작하지 않아, 원인 파악을 하다가 중국은 한국과 다르게 CGNAT이라는 시스템을 사용해서 IPv4 주소를 할당하는 것을 확인했습니다. 중국엔 사람이 너무 많아서 이 방법이 중국 네트워크 구축에 유일한 솔루션이겠지만, 중국 통신사들이 이 방법을 사용하면서 생긴 문제가 홈 네트워크로 직접 연결이 막혀버린다는 점입니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;nat-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;4032&quot; height=&quot;3024&quot; src=&quot;/_astro/nat-1.DtaeiAHk_ZKS06L.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;다행히도 이 문제에 대한 해결방안이 있는데, 바로 SSH를 통한 포트 포워딩입니다. SSH를 사용해 외부 서버로 연결을 한다면 외부 서버에 접수되는 요청을 SSH를 통해 홈 서버로 포워드하면 됩니다.&lt;/p&gt;
&lt;p&gt;이 사용기에서 어떻게 이렇게 외부 서버를 구축하는지, 그리고 어떻게 포워딩 설정을 하는지 설명하겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;1-digitalocean&quot;&gt;1. DigitalOcean&lt;/h1&gt;
&lt;p&gt;일단 외부 서버가 필요한데, 저는 이 사용기에서 VPS (Virtual Private Server, 클라우드 서버)를 사용하겠습니다. DigitalOcean이란 사이트에서 VPS를 쉽게 주문할 수 있는데, 사이트에 회원가입을 한 다음, 신용카드 정보를 입력한 후 인스턴스를 하나 생성합니다. 여기에서 크기는 별로 중요하지 않기에 가장 작은 인스턴스를 선택했는데, 한 달에 5달러 정도 청구됩니다.&lt;/p&gt;
&lt;p&gt;인스턴스를 생성한 다음 IP 주소를 기억해둡니다. 이 사용기에서 이 IP를 &lt;code&gt;XXX.XXX.XXX.XXX&lt;/code&gt;로 표기하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;nat-2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;4032&quot; height=&quot;3024&quot; src=&quot;/_astro/nat-2.Dvjl-FvT_159aip.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;2-서버-설정&quot;&gt;2. 서버 설정&lt;/h1&gt;
&lt;p&gt;이제 이 VPS 인스턴스에 &lt;code&gt;ssh&lt;/code&gt;로 연결한 다음, 초기 설정을 해 줍니다. DigitalOcean에서 &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-create-a-sudo-user-on-ubuntu-quickstart&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;빠른 시작 가이드가 있는데&lt;/a&gt;, 이 가이드를 사용해서 sudo 사용자를 만들고 기타 보안 설정을 해 줍니다. 설정이 완료되면 다음 단계로 넘어갑니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; 파일은 수정해서 &lt;code&gt;#GatewayPorts no&lt;/code&gt;를 &lt;code&gt;GatewayPorts yes&lt;/code&gt;로 변경해줍니다. (앞에 있는 해시태그를 삭제한 다음 &lt;code&gt;no&lt;/code&gt;를 &lt;code&gt;yes&lt;/code&gt;로 변경합니다.)&lt;/p&gt;
&lt;p&gt;파일을 저장한 다음, &lt;code&gt;sudo service sshd restart&lt;/code&gt;를 사용해서 &lt;code&gt;ssh&lt;/code&gt; 서버 서비스를 재시작시킵니다. 이때 연결이 잠시 끊길 수 있는데, 다시 접속하시면 됩니다. 연결이 되었으면 서버를 재부팅합니다.&lt;/p&gt;
&lt;h1 id=&quot;3-클라이언트-설정&quot;&gt;3. 클라이언트 설정&lt;/h1&gt;
&lt;p&gt;이제 홈 서버/연결하고 싶은 클라이언트에서 설정을 시작합니다. 예를 들어, 집에 있는 서버의 9000번 포트에 외부 서버의 80번 포트를 연결해보겠습니다. 연결 경로는 다음과 같습니다:&lt;/p&gt;
&lt;p&gt;사용자 —&gt; 외부 서버 &lt;code&gt;XXX.XXX.XXX.XXX&lt;/code&gt; (포트 &lt;code&gt;80&lt;/code&gt;) —&gt; 홈 서버 (포트 &lt;code&gt;9000&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;nat-3&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;4032&quot; height=&quot;3024&quot; src=&quot;/_astro/nat-3.Du7_BJH0_1BoLt5.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이렇게 연결을 설정하려면 &lt;strong&gt;홈 서버&lt;/strong&gt;에서 다음 SSH 명령을 입력합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ssh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -nNT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -R&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 80:localhost:9000&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; username@XXX.XXX.XXX.XXX&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 명령을 해석해보겠습니다. &lt;code&gt;ssh&lt;/code&gt;는 설명이 필요없고, &lt;code&gt;-nNT&lt;/code&gt;는 SSH 프로그램에게 TTY 인스턴스를 생성하지 말라고 지시합니다 (그래서 이 명령을 실행하면 터미널 프롬프트가 나타나지 않습니다). &lt;code&gt;-R&lt;/code&gt;은 SSH에게 원격 포트 포워드를 요청하고, 그 다음에 오는 부분이 무슨 포트를 어디로 포워드할지 설정해줍니다. 여기서 &lt;code&gt;80&lt;/code&gt;은 외부 서버의 포트고, &lt;code&gt;localhost&lt;/code&gt;는 현재 홈 서버를 가르키며, &lt;code&gt;9000&lt;/code&gt;은 외부 서버에 도착한 요청을 무슨 포트로 포워드해줄지 알려줍니다. 그 이후의 구문은 (&lt;code&gt;username@XXX.XXX.XXX.XXX&lt;/code&gt;) 그냥 일반적으로 SSH로 서버에 연결할 때 사용되는 인증 정보와 서버 주소입니다.&lt;/p&gt;
&lt;p&gt;다른 예시를 들어보자면, 홈 네트워크의 주소 &lt;code&gt;192.168.0.102&lt;/code&gt;에 다른 장치가 있고, 이 장치에서 포트 &lt;code&gt;12984&lt;/code&gt;에 웹 서버가 돌아가고 있다고 가정합니다. 만약 외부 서버의 포트 &lt;code&gt;80&lt;/code&gt;과 &lt;code&gt;443&lt;/code&gt;을 통해 이 장치에 연결하고 싶다면, 홈 서버에서 다음 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ssh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -nNT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -R&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 80:192.168.0.102:12984&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; username@XXX.XXX.XXX.XXX&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # HTTP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ssh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -nNT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -R&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 443:192.168.0.102:12984&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; username@XXX.XXX.XXX.XXX&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # HTTPS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ssh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -nNT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -R&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 80:192.168.0.102:12984&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -R&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 443:192.168.0.102:12984&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; username@XXX.XXX.XXX.XXX&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # 아니면 한번에 다&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;연결 경로는 다음 그림과 같습니다:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;nat-4&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;4032&quot; height=&quot;3024&quot; src=&quot;/_astro/nat-4.BoT-wsFj_1BiNOk.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;4-autossh&quot;&gt;4. AutoSSH&lt;/h1&gt;
&lt;p&gt;이제 이 연결을 자동화해주는 서비스를 사용해보겠습니다. 매번 SSH 명령을 실행할 필요없이 포워드를 해주는 서비스 AutoSSH를 설치합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; autossh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우분투 서버가 아닌 경우 알맞는 설치 툴로 설치합니다. 다음으로, &lt;code&gt;systemd&lt;/code&gt; 설정 파일을 만들어줍니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;nano&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /etc/systemd/system/autossh-tunnel.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다음을 붙여넣기한 다음, 사용 환경에 알맞게 수정해줍니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Unit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Description=AutoSSH tunnel service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;After=network.target&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Service]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Environment=&quot;AUTOSSH_GATETIME=0&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ExecStart=/usr/bin/autossh -M 0 -o &quot;ServerAliveInterval 30&quot; -o &quot;ServerAliveCountMax 3&quot; -o &quot;ExitOnForwardFailure yes&quot; -N -R 80:192.168.0.1:80 -R 443:192.168.0.1:443 root@server.example.com -p 22 -i /home/example/.ssh/private-key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Install]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;WantedBy=multi-user.target&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 &lt;code&gt;systemd&lt;/code&gt;에게 새 설정을 확인하라고 지시한 다음 설정을 활성화해줍니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; daemon-reload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; autossh-tunnel.service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enable&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; autossh-tunnel.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 SSH 연결도 자동화하고 싶다면, &lt;code&gt;~/.ssh/config&lt;/code&gt;을 수정하면 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Host example-tunnel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    HostName server.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    User example&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Port 22&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    IdentityFile ~/.ssh/private-key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    RemoteForward 80 192.168.0.1:80&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    RemoteForward 443 192.168.0.1:443&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ServerAliveInterval 30&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ServerAliveCountMax 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ExitOnForwardFailure yes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기에서 &lt;code&gt;RemoteForward&lt;/code&gt;는 별도로 적을 필요가 없는게, AutoSSH가 포워드를 담당하기에 두 번 적는데 큰 의미는 없습니다.&lt;/p&gt;
&lt;p&gt;AutoSSH 설정 부분은 &lt;a href=&quot;https://www.everythingcli.org/ssh-tunnelling-for-fun-and-profit-autossh/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;이 가이드에서 참고를 많이 했습니다.&lt;/a&gt; 만약 추가로 설정하고 싶으신 부분이 있다면 확인해보세요! 원글 작성자에게 감사드립니다.&lt;/p&gt;</content:encoded></item><item><title>git 커밋 GPG로 서명하기</title><link>https://ericswpark.com/ko/blog/2018/2018-05-13-force-git-to-sign-your-commits/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2018/2018-05-13-force-git-to-sign-your-commits/</guid><pubDate>Sun, 13 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;GPG로 git 커밋에 서명하면 깃허브에서 “Verified” 배지가 붙습니다. 하지만 매번 &lt;code&gt;git -S&lt;/code&gt;를 입력하려니 꽤 귀찮은데, 이 튜토리얼로 다시는 &lt;code&gt;-S&lt;/code&gt; 태그 입력 없이 커밋에 서명할 수 있습니다!&lt;/p&gt;
&lt;p&gt;다음 명령을 실행합니다 (두 번째 명령에서 키는 당연히 변경해야 합니다):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; config&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --global&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; commit.gpgsign&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; config&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --global&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user.signingkey&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;키&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; I&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;D&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GPG 키 ID를 잊었다면:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --list-keys&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --keyid-format&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; short&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GPG 사용 방법을 모르시면, &lt;a href=&quot;/ko/blog/2020/2020-02-25-gpg-the-complete-crash-course/&quot;&gt;제 GPG 가이드를 확인해보세요.&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;문제-해결&quot;&gt;문제 해결&lt;/h1&gt;
&lt;p&gt;만약 &lt;code&gt;git&lt;/code&gt; 커밋시 다음 에러가 발생한다면:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;error: gpg failed to sign the data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fatal: failed to write commit object&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;gpg&lt;/code&gt;가 쉘 인스턴스를 찾지 못해서 발생하는 문제입니다. 수정하려면 다음 줄을 사용하는 쉘에 따라 &lt;code&gt;~/.bashrc&lt;/code&gt;나 &lt;code&gt;~/.zshrc&lt;/code&gt;에 추가합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; GPG_TTY&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tty&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Visual Studio Code 사용 시 터미널 윈도우 크기가 프롬프트보다 작은 경우에도 에러가 발생할 수 있습니다. 터미널 윈도우의 크기를 키우면 문제가 해결됩니다.&lt;/p&gt;
&lt;h2 id=&quot;윈도우&quot;&gt;윈도우&lt;/h2&gt;
&lt;p&gt;만약 GPG4Win을 사용하여 GPG 키들을 관리하신다면, 다음 명령을 터미널에서 실행해야 할 수도 있습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git config --global gpg.program &quot;C:\Program Files (x86)\GnuPG\bin\gpg.exe&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 명령을 실행하면 &lt;code&gt;git&lt;/code&gt;이 Git for Windows와 같이 오는 &lt;code&gt;gpg.exe&lt;/code&gt; 실행파일 대신 GPG4Win의 실행파일을 사용하게 됩니다.&lt;/p&gt;</content:encoded></item><item><title>우분투 서버로 네트워크 전체에 VPN 연결 구축하기</title><link>https://ericswpark.com/ko/blog/2018/2018-05-01-set-up-a-network-wide-vpn-using-ubuntu-server/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2018/2018-05-01-set-up-a-network-wide-vpn-using-ubuntu-server/</guid><pubDate>Tue, 01 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;중국에서 외국인으로 살다 보면, YouTube와 Netflix를 보려고 VPN 어플로 전환하는 과정이 진짜 귀찮아집니다.&lt;/p&gt;
&lt;p&gt;더 이상 VPN 어플을 사용하지 않도록 이번 글에서는 네트워크 전체에 VPN 연결을 구축해보겠습니다!&lt;/p&gt;
&lt;h1 id=&quot;준비물&quot;&gt;준비물&lt;/h1&gt;
&lt;p&gt;준비물은 다음과 같습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OpenVPN 설정을 제공하는 VPN 업체. 다른 프로토콜도 아마 작동하겠지만, 이 글에선 다루지 않습니다.&lt;/li&gt;
&lt;li&gt;우분투 서버가 깔려있는 서버나 가상머신. 최신 버전을 사용하는 것을 추천합니다. 이 글은 16.04 LTS 버전을 기준으로 작성되었습니다.&lt;/li&gt;
&lt;li&gt;리눅스와 네트워킹 지식&lt;/li&gt;
&lt;li&gt;(필수는 아니지만 추천) DHCP 서버의 기본 게이트웨이 설정을 변경할 수 있는 라우터&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;리눅스와 네트워킹 지식이 없다면 나중에 인터넷 연결이 작동하지 않을 수도 있습니다! 이 글을 따라하기 전에 관련 정보를 검색하고 진행하세요.&lt;/p&gt;
&lt;p&gt;준비물 중, DHCP 서버의 기본 게이트웨이 설정을 변경할 수 있는 라우터가 중요합니다. 라우터가 이 설정을 지원하지 않는다면 모든 기기에 네트워크 설정을 변경하여 새 리눅스 서버로 네트워크 트래픽을 우회시켜야 합니다. 차라리 그냥 각 기기마다 VPN 어플을 설치하는게 더 쉽겠지만, 이 글은 VPN을 지원하지 않는 기기에게도 매우 유용하기 때문에 한번 시도해보시는 걸 추천합니다.&lt;/p&gt;
&lt;p&gt;주말에 한번 취미삼아 구축하던, 진짜 필요해서 구축하던, VPN 서버는 정말 유용하게 써먹습니다. 단 구축후 생길 문제에 대비해 어떻게 다시 네트워크를 초기화하는지 알고 넘어가시면 좋습니다.&lt;/p&gt;
&lt;h1 id=&quot;vpn-설정&quot;&gt;VPN 설정&lt;/h1&gt;
&lt;p&gt;OpenVPN 파일을 VPN 제공업체에서 다운받습니다. 다운로드 페이지에 만약 아이디와 비밀번호가 나열되어 있는 경우 옆에 적어둡니다.&lt;/p&gt;
&lt;p&gt;(만약 아이디와 비밀번호가 별도로 나와있지 않으면, VPN 제공업체에 로그인하는 계정의 아이디와 비밀번호일 수도 있습니다. 나중에 실헝해보고 변경하세요.)&lt;/p&gt;
&lt;p&gt;VPN 제공업체에 따라서 한 통합 OpenVPN 파일을 제공할 수도 있고, 아니면 여러개로 나뉘어진 설정 파일들을 제공할 수도 있습니다. 나중에 키 설정하고 잡다한 설정을 건드리지 않아도 되기에 되도록이면 통합되어 있는 파일을 받는 것이 좋습니다.&lt;/p&gt;
&lt;p&gt;이제 VM이나 서버로 이동해서 다음 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ifconfig&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;현재 네트워크 설정이 다음과 같이 나타납니다. 왼쪽에 있는 네트워크 인터페이스 이름을 적어둡니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;enp2s0    Link encap:Ethernet  HWaddr &amp;#x3C;숨김&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            inet addr:192.168.0.100  Bcast:192.168.0.255  Mask:255.255.255.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;생략&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;lo        Link encap:Local Loopback  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            inet addr:127.0.0.1  Mask:255.0.0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;생략&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예시의 네트워크 인터페이스 이름은 &lt;code&gt;enp2s0&lt;/code&gt;이고, IP 주소는 &lt;code&gt;192.168.0.100&lt;/code&gt;으로 표시되는데, 나중에 사용하기 위해 일단 메모해둡니다.&lt;/p&gt;
&lt;p&gt;OpenVPN을 설치합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; openvpn&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;설치가 완료되면 설정을 시작합니다. 주어진 통합 설정 파일을 복사합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; cp&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;VPN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 이&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;름&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.ovpn&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /etc/openvpn/&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;VPN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 이&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;름&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 여러개로 나뉘어진 설정 파일들을 &lt;code&gt;.ovpn&lt;/code&gt; 파일과 함께 받았을 경우, 키 파일을 확인합니다. 이름이 &lt;code&gt;ca.rsa.2048.crt&lt;/code&gt;나 비슷하면 필요한 키 파일이 맞습니다. (2048은 keysize로, VPN 제공업체마다 다를 수 있습니다.) 키 파일을 찾은 후 복사합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; cp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ca.rsa.2048.crt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; crl.rsa.2048.pem&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /etc/openvpn&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 통합 OpenVPN 파일을 받았다면 이 과정을 건너뛰어도 됩니다.&lt;/p&gt;
&lt;p&gt;여기에서 왜 &lt;code&gt;.ovpn&lt;/code&gt; 파일을 &lt;code&gt;.conf&lt;/code&gt;로 확장자를 변경했냐면, 이렇게 수정해야 &lt;code&gt;systemd&lt;/code&gt;로 OpenVPN을 제어할 수 있기 때문입니다. 이 변경으로 설정 파일이 &lt;code&gt;systemctl&lt;/code&gt;에 등록되고, 실행할 때 &lt;code&gt;OpenVPN@VPN-이름&lt;/code&gt;으로 활성화할 수 있습니다.&lt;/p&gt;
&lt;p&gt;다음으로, VPN 아이디와 비밀번호를 &lt;code&gt;/etc/openvpn/login&lt;/code&gt;에 적습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;아이디-여기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;비밀번호-여기&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예를 들어, 이렇게 적으면 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ericswpark&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;examplepassword12345&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;저장하고 나간 다음, &lt;code&gt;/etc/openvpn/&amp;#x3C;VPN 이름&gt;.conf&lt;/code&gt;을 열고 다음 줄을 편집합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auth-user-pass&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 변경합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auth-user-pass /etc/openvpn/login&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아까 만약 키 파일이 분리되어 있는 파일을 받으셨다면 다음 줄들도 수정해야 합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ca ca.rsa.2048.crt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;crl-verif crl.rsa.2048.pem&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 변경하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ca /etc/openvpn/ca.rsa.2048.crt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;crl-verif crl.rsa.2048.pem&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 VPN 설정이 완료되었습니다!&lt;/p&gt;
&lt;p&gt;저장하시기 전에, 추가적으로 몇가지 유용한 설정들이 있습니다. 예를 들어, 인터넷 연결이 불안정한 곳이라면 &lt;code&gt;persist-tun&lt;/code&gt;을 비활성화하는 것이 좋습니다. 이 설정을 비활성화한다면 VPN이 재시작시 터널 인터페이스를 삭제하고 원래 인터넷 연결로 VPN 서버를 검색합니다. 이게 왜 유용하냐면 터널 인터페이스가 죽어있다면 이 인터페이스로 VPN 서버를 검색할 수 없기 때문입니다.&lt;/p&gt;
&lt;p&gt;다음 줄을 찾고:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;persist-tun&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;줄 앞에 &lt;code&gt;#&lt;/code&gt;을 적어 비활성화시킵니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# persist-tun&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 서버가 연결을 활성 상태로 유지하게 만들고 싶다면 다음 줄들을 찾거나 추가합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;keepalive 10 30&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;resolv-retry infinite&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;마지막 줄은 기본 설정이 어쩌피 무한이기 때문에, 꼭 필요하지는 않습니다. 첫 번째 줄은 OpenVPN 클라이언트에게 VPN 서버를 10초마다 핑(&lt;code&gt;--ping&lt;/code&gt;)하게 만들고, 만약 응답이 없을 경우 30초마다 재시작(&lt;code&gt;--ping-restart&lt;/code&gt;)을 하게 만듭니다. 중국에선 황금방패 (Great Firewall of China)가 TCP RST 패킷을 수시로 보내거나 조용히 연결을 죽이기 때문에 이 설정이 매우 유용합니다. 설정값을 조절하면서 연결이 안정적이도록 수정할 수 있지만, 제 경험으로 30초 정도면 VPN 서버에 적당한 부하를 걸면서 충분히 안정적인 연결을 유지할 수 있습니다.&lt;/p&gt;
&lt;p&gt;저장하고 VPN 서버를 테스트합니다. 중국에선 그냥 구글을 핑하면 확인할 수 있지만, 만약 이 튜토리얼을 중국이 아닌 다른 나라에서 진행한다면 &lt;code&gt;ifconfig&lt;/code&gt;을 다시 확인해도 됩니다.&lt;/p&gt;
&lt;p&gt;클라이언트를 실행시킵니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; openvpn&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --config&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /etc/openvpn/&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;VPN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 이&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;름&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로그에서 &lt;code&gt;Initialization Sequence Completed&lt;/code&gt;라는 문구가 나온다면 VPN이 정상적으로 동작한다는 것입니다. Ctrl-C를 눌러 VPN 터널을 죽이고 다음으로 넘어갑니다.&lt;/p&gt;
&lt;h1 id=&quot;수동-ip-설정하기-필수는-아니지만-추천&quot;&gt;수동 IP 설정하기 (필수는 아니지만 추천)&lt;/h1&gt;
&lt;p&gt;이제 VM이너 서버에 수동 IP를 설정하는 것을 추천합니다. 나중에 기본 게이트웨이 설정에서 IP가 변경되면 일이 매우 귀찮아지기 때문입니다.&lt;/p&gt;
&lt;p&gt;라우터 관리 페이지에 접속합니다. 가정 라우터는 거의 대부분 라우터 밑 스티커에 정보가 인쇄되어 있습니다. 만약 스티커를 때어버렸다면 (…), 구글에서 라우터 제조사를 검색한 다음 관리 IP 주소를 찾으면 됩니다. 그마저도 없다면 대부분의 라우터는 기본 게이트웨이가 관리 페이지입니다. 그것도 안 된다면 수동으로 IP 주소를 하나하나 확인합니다: &lt;code&gt;192.168.0.1&lt;/code&gt;, &lt;code&gt;192.168.1.1&lt;/code&gt;, …&lt;/p&gt;
&lt;p&gt;이 라우터 IP를 적어둡니다. 관리 페이지에 접속한 후 DHCP 주소 할당이나 비슷한 메뉴로 들어갑니다. (국내 라우터는 대부분 수동 IP 할당 이렇게 메뉴가 있습니다.) 아까 맨 앞에 MAC 주소를 기억한다면 이제 여기에 입력을 하면 됩니다. (안 적어 두었다면 다시 VM이나 서버에서 &lt;code&gt;ifconfig&lt;/code&gt;를 확인합니다.)&lt;/p&gt;
&lt;p&gt;주소를 적은 다음에 적용합니다. 이제 VM이나 서버에다가 설정을 더 추가해야 합니다. &lt;code&gt;/etc/network/interfaces&lt;/code&gt; 에서 다음 줄들을 찾습니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auto enp2s0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;iface enp2s0 inet dhcp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;인터페이스 이름에 차이가 있을 수 있습니다. 다음과 같이 수정하세요 (인터페이스 이름은 원래 있던 그대로 두어야 합니다):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auto enp2s0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;iface enp2s0 inet static&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    address 192.168.0.100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    netmask 255.255.255.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gateway 192.168.0.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    dns-nameservers 1.1.1.1 1.0.0.1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DNS 서버는 아무거나 괜찮은데, 전 여기에서 CloudFlare의 &lt;code&gt;1.1.1.1&lt;/code&gt;로 설정했습니다. 여기에서 올바른 IP 주소와 넷마스크를 입력했는지 확인하세요! IP 주소는 아까 라우터 관리자 페이지에서 적어두었던 수동 IP 주소를 입력하면 되고, 넷마스크와 기본 게이트웨이는 현재 인터넷 설정에서 찾으면 됩니다 (거의 대부분 라우터의 IP입니다.)&lt;/p&gt;
&lt;p&gt;기본 게이트웨이 설정이 중요한데, 설정 없이 나중에 라우터 설정에서 DHCP 서버 설정을 변경해 VM/서버의 IP 주소를 뿌리게 된다면 VM/서버에서 자기 자신으로 기본 게이트웨이가 설정됩니다. 그래서 기본 게이트웨이가 현재 설정되어 있는 게이트웨이와 같은지 확인하는 것이 중요합니다.&lt;/p&gt;
&lt;p&gt;설정이 완료되면 라우터와 VM/서버를 차례대로 재부팅 후, 인터넷이 동작하는지 확인합니다.&lt;/p&gt;
&lt;h1 id=&quot;터널-설정하기&quot;&gt;터널 설정하기&lt;/h1&gt;
&lt;p&gt;일단 OpenVPN 서비스를 부팅 시 실행되도록 활성화시킵니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enable&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OpenVPN@&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;VPN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; her&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 명령을 실행할 때 &lt;code&gt;.conf&lt;/code&gt; 확장자를 적으면 안 됩니다!&lt;/p&gt;
&lt;p&gt;다음으로 IPv4 포워딩을 설정해야 됩니다. &lt;code&gt;/etc/sysctl.conf&lt;/code&gt;을 연 다음 다음 줄을 찾아서:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# net.ipv4.ip_forward = 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다음과 같이 변경합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;net.ipv4.ip_forward = 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;#&lt;/code&gt;을 제거하고, 저장후 넘어갑니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sysctl&lt;/code&gt;에 새 설정을 적용시킵니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; sysctl&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 방화벽과 라우팅을 설정하도록 하겠습니다.&lt;/p&gt;
&lt;h1 id=&quot;iptables&quot;&gt;&lt;code&gt;iptables&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;라우팅 테이블 설정이 이 튜토리얼에서 가장 어렵습니다. 저도 그렇게 이해가 완전히 되진 않지만 한번 설명해보겠습니다.&lt;/p&gt;
&lt;p&gt;일단 루프백 (loopback) 인터페이스를 사용한 루프백 트래픽을 허용해야 합니다. 다음 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; INPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lo&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;loopback-input&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OUTPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lo&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;loopback-output&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다음으로 네트워크에 들어오는 트래픽과 터널 인터페이스로 나가는 트래픽을 허용해야 합니다. (인터페이스 이름을 잊어버렸다면, 다시 VPN 연결을 위에 있는 명령으로 활성화시킨 다음, &lt;code&gt;ifconfig&lt;/code&gt;으로 인터페이스 이름을 확인하면 됩니다.) 인터페이스 이름을 바꾼 다음 명령들을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -I&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; INPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enp2s0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Local network&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -I&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OUTPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tun0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;VPN network&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 OpenVPN의 트래픽을 허용해야 합니다. &lt;code&gt;.conf&lt;/code&gt; 파일에서 포트 번호를 확인한 다음 (&lt;code&gt;remote&lt;/code&gt;로 시작하는 줄에 있습니다) 포트 번호와 인터페이스 이름을 바꾼 후 다음 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OUTPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enp2s0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; udp&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --dport&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1194&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;OpenVPN traffic&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 서버의 NTP/DNS/DHCP 서비스를 허용해야 합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OUTPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; eth0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; udp&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --dport&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 123&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;NTP service&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OUTPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; UDP&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --dport&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 67:68&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;DHCP service&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OUTPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; eth0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; udp&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --dport&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 53&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;DNS service&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;거의 다 됐습니다. 이제 터널의 트래픽을 이더넷 인터페이스로 포워드해야 합니다. 이 부분이 가장 중요합니다. 인터페이스 이름을 바꾼 후 다음 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FORWARD&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tun0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enp2s0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; state&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --state&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; RELATED,ESTABLISHED&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FORWARD&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enp2s0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tun0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Local network to VPN&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;마지막으로 NAT (Network Address Translation)이 VPN에서 동작하도록 &lt;code&gt;POSTROUTING&lt;/code&gt; 테이블에서 &lt;code&gt;MASQUERADE&lt;/code&gt;를 설정해야 합니다. 인터페이스 이름을 바꾼 후 다음 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -t&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; nat&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; POSTROUTING&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tun0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; MASQUERADE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기까지 왔다면 축하합니다! 하지만 서버에서 로그아웃하기 전에 이 설정을 저장해야 재부팅 시 설정이 유지됩니다.&lt;/p&gt;
&lt;p&gt;다음 프로그램을 설치합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables-persistent&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;설치하면서 현재 설정 사항을 저장할지 묻는다면 예를 선택하고 진행합니다. 서비스가 부팅 시 시작되도록 다음 명령을 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enable&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; netfilter-persistent&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;나중에 &lt;code&gt;iptables&lt;/code&gt; 룰을 더 추가한다면 이 명령을 실행하면 됩니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; netfilter-persistent&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; save&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 설정값이 재부팅 시 적용됩니다.&lt;/p&gt;
&lt;p&gt;마지막으로, 설정을 백업하고 싶으시다면 다음을 실행하면:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables-save&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;설정값이 텍스트 파일에 저장됩니다. 저처럼 &lt;code&gt;iptables&lt;/code&gt;에 자신이 없으시다면 이렇게 백업을 하신 다음 백업 파일을 편집하신 후 다시 적용해도 됩니다. 적용할 때엔 다음 명령을 사용합니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables-restore&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 설정 부분은 다 마쳤습니다.&lt;/p&gt;
&lt;h1 id=&quot;연결-확인하기&quot;&gt;연결 확인하기&lt;/h1&gt;
&lt;p&gt;서버를 재시작한 다음, 확인하고 싶은 기기에서 기본 게이트웨이의 주소를 서버/VM의 IP로 변경합니다. 그럼 기기의 트래픽이 VPN을 통해 전송됩니다. 여기까지 된다면 성공한 것입니다!&lt;/p&gt;
&lt;p&gt;하지만 모든 기기마다 기본 게이트웨이를 변경하는 건 귀찮습니다. 그래서…&lt;/p&gt;
&lt;h1 id=&quot;라우터-포워딩-설정하기&quot;&gt;라우터 포워딩 설정하기&lt;/h1&gt;
&lt;p&gt;라우터에서 기본 게이트웨이 설정의 변경을 허용하는 경우, 이 부분도 따라하실 수 있습니다. (거의 대부분의 라우터가 이 설정을 지원합니다.)&lt;/p&gt;
&lt;p&gt;아까 라우터 관리자 페이지에 접속 후, DHCP 설정을 선택합니다. 기본 게이트웨이 IP 주소 아래 VM/서버의 IP 주소를 입력합니다.&lt;/p&gt;
&lt;p&gt;설정을 저장한 후, 라우터를 재부팅시킵니다. 이제 연결된 기기를 확인합니다. 만약 인터넷 연결이 정상적으로 동작한다면 완료된 것입니다!&lt;/p&gt;
&lt;p&gt;만약 기본 게이트웨이 IP 주소를 변경했는데 관리자 페이지의 IP 주소가 새 기본 게이트웨이 IP 주소로 변경된다면, 주소 충돌로 인해 VM/서버가 IP 주소를 잃거나 로컬 네트워크에서 연결이 끊길수도 있습니다. 최악의 경우에는 라우터의 관리 페이지가 동작하지 않을수도 있습니다. 만약 이런 문제가 발생한다면, VM/서버를 네트워크에서 분리하고, 라우터를 재시작한 후 관리자 페이지에서 기본 게이트웨어 설정을 원래대로 변경하면 됩니다. 이렇게 설정 변경이 안 된다면 라우터에서 기본 게이트웨이 설정 변경을 지원하지 않아서 발생하는 문제입니다.&lt;/p&gt;
&lt;h1 id=&quot;연결-도우미-스크립트&quot;&gt;연결 도우미 스크립트&lt;/h1&gt;
&lt;p&gt;중국에선 수시로 VPN 연결을 끊어먹기 때문에, 매번 VM/서버에 다시 접속해서 연결을 재시작시켜주는 것이 귀찮아서 &lt;a href=&quot;https://github.com/ericswpark/python_vpn-client&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;파이썬으로 연결 과정을 도와주는 스크립트를 작성했습니다.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;만약 추가로 변경 사항이 있다면 깃허브 pull request로 보내주세요!&lt;/p&gt;</content:encoded></item><item><title>Swift에서 NSSavePanel 사용하기</title><link>https://ericswpark.com/ko/blog/2018/2018-02-28-use-nssavepanel-in-swift/</link><guid isPermaLink="true">https://ericswpark.com/ko/blog/2018/2018-02-28-use-nssavepanel-in-swift/</guid><pubDate>Wed, 28 Feb 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;요약본:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;@IBAction&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; browseDirectory&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sender: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dialog &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; NSSavePanel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dialog.title &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;파일 저장&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dialog.showsResizeIndicator &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dialog.canCreateDirectories &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dialog.showsHiddenFiles &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dialog.allowedFileTypes &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;txt&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (dialog.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;runModal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; NSApplication.ModalResponse.OK) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dialog.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; path &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.path &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// &apos;path&apos; 변수로 작업하기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // 사용자가 모달을 취소함&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;파일시스템 사용 권한을 설정하는 것도 잊지 마세요.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;일단 모달을 생성해야 되니까 ‘dialog’ 변수를 만들고 NSSavePanel을 추가해줍니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dialog &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; NSSavePanel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;모달의 설정을 변경합니다. 일단 모달의 제목 바 이름을 설정하세요:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dialog.title &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;파일 저장&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다음 모달 설정은 제목 바에 “크기 조절” 버튼을 보일지, 사용자가 새로운 폴더를 추가하도록 허용할지, 그리고 숨겨진 파일을 보일지 설정합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dialog.showsResizeIndicator &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dialog.canCreateDirectories &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dialog.showsHiddenFiles &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;사용자가 특정 파일만 선택할 수 있게 하려면:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dialog.allowedFileTypes &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;php&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;마지막으로 사용자에게 모달을 보입니다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (dialog.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;runModal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; NSApplication.ModalResponse.OK) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dialog.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; path &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.path &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// &apos;path&apos; 변수로 작업하기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // 사용자가 모달을 취소함&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하지만 이렇게 코드를 짜놓고 어플을 실행하면 바로 크래시가 발생합니다. 이유는 어플이 파일시스템을 사용할 수 있도록 권한을 설정하는 것을 깜빡했기 떄문입니다. 왼쪽 사이드바에 있는 프로젝트를 클릭한 후, 작은 사이드바의 “타겟” 밑의 어플을 선택하고, 권한을 설정하주면 됩니다. (제 Xcode가 영어로 설정되어 있어서 한국어 메뉴는 이름이 살짝 다를 수 있습니다.)&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;xcode-nssavepanel-permissions&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1003&quot; height=&quot;474&quot; src=&quot;/_astro/xcode-nssavepanel-permissions.DKs08D04_Z2d6Rye.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이제 저장 모달이 완성되었습니다!&lt;/p&gt;</content:encoded></item></channel></rss>