지난 여름과 가을은 대부분 Metapho를 Swift 6에 맞추어 업데이트하면서 보냈습니다. 그 과정에서 Swift concurrency 망치를 손에 들고 보니 모든 문제가 이 망치로 두들기면 해결될 것 같다는 망상에 빠집니다. 그리하여 블로그 엔진을 또 갈아엎는 작업에 착수하였습니다.
어디서 돌리나
마침 12.3 내란 때문에 치솟은 환율 탓에 월 5달러인 Linode 가상 사설 서버 유지비도 약간 아깝게 느껴지기 시작했습니다. 과감하게 라즈베리 파이5 8기가 버전을 구입, 집에서 모든 것을 돌리는 쪽으로 방향을 잡았습니다.
웹 프레임워크 바꾸기
본 블로그가 돌아가고 있는 엔진 MoonBurst는 Vapor라는 Swift 웹 프레임워크를 쓰고 있었습니다만, Vapor는 너무 덩치가 커서 그런지 structured concurrency 지원이 늦어지고 있어 기다리다 지친 저는 HummingBird 라는 좀 더 가벼워 보이는 녀석에 관심을 갖게 됩니다. 그래서 HummingBird 2를 이용하여 블로그 엔진을 다시 썼습니다.
라즈베리 파이의 운영체제가 평범한 Debian으로 바뀌면서 MoonBurst 첫 번째 버전을 만들던 2018년에 비하면 라즈베리 파이에서 Swift를 돌아가게 하기가 아주 수월해졌습니다. 공식 문서에서 하라는 것만 해주면 잘 돌아가서 조금 놀랐습니다. Markdown을 HTML 바꾸는 작업도 무려 애플에서 공식 swift-markdown 라이브러리를 유지하는 중이라 아주 편합니다. (라고 하기엔 코드블록에서 HTML entity escape를 안해주는 함정이 있습니다만, 자세한 것은 나중에…)
Swift 빌드 시간도 라즈베리 파이5의 위력으로 단 600초 정도면 의존성 다운로드부터 완성까지 잘 마칩니다. 31초 정도 걸리는 맥미니에 비하면 느리지만 뭐 나쁘지 않지요.
약간의 양계질이 있었지만, 어찌저찌 엔진을 완성하였습니다. 여전히 큰 트래픽은 Siege를 이용한 자체 DDoS 테스트를 할 때만 발생하지만(네, 섣부른 최적화 맞습니다), 아무튼 덜 죽고 더 튼튼하고 빠른 엔진을 (또) 마련했으니 이제 게시글만 좀 쓰면 되겠습니다.
어떻게 돌리나
작고 귀여운 서버를 집에서 돌리려면 여러가지 걸림돌이 있습니다. 유동 아이피니 포트 포워딩이니 귀찮은 것들이 귀찮았는데, 연구 결과 Cloudflare Tunnel이라는 서비스에 대하여 알게 되었습니다. 웹에서의 간단한 설정과 라즈베리 파이에서의 간단한 설치만 거치면 편리하고 안전하게 localhost로 열어둔 웹 서버를 외부에 공개할 수 있습니다. https 설정도 Cloudflare에서 알아서 해주니 전에 쓰던 letsencrypt와 nginx의 조합을 전부 대체할 수 있었습니다. 무료 플랜도 아주 너그러워서 이게 맞나? 싶기도 합니다.
라즈베리 파이로 웹 서버를 24시간 돌리니 이제야 ISP에 꼬박꼬박 내는 사용료가 조금 덜 아까워지는 기분이 듭니다.
iOS 앱 개발을 하다보면 가끔 겉보기로는 별것 아닌 일에 막대한 시간을 들여 강도 높은 삽질을 하곤 하는데, 기록 차원에서 여기에 좀 남겨야겠다.
Metapho 3.2.4에서 최소 요구사항을 iOS 12로 올렸더니 NSSecureCoding과 관련된 많은 메소드들이 iOS 12에서 deprecated 되었다고 Xcode가 경고한다. WWDC2018 영상을 찾아보니 이것은 NSSecureCoding을 좀 더 권장하기 위한 결정이었다.
핵심은 archive할 때 어떤 클래스가 들어 있었는지 unarchive할 때 지정해 주라는 것.
그러나 하라는데로 했는데 Data를 NSArray로 바꾸는 것이 영 안 된다.
let array = [CLLocation(latitude: 0, longitude: 0)]
let data = try! NSKeyedArchiver.archivedData(withRootObject: array, requiringSecureCoding: true)
let unarchivedArray = try! NSKeyedUnarchiver.unarchivedObject(ofClass: NSArray.self, from: data)
// Fatal error
한참을 삽질하다가 애플 개발자 포럼의 포스트 하나를 보고 대오각성. CLLocation같은 object들이 들어있는 Collection (Array, Dictionary, Set)을 unarchive할 때는 unarchiveObject(ofClass:from:)이 아니라 unarchiveObejct(ofClasses:from:)에서 ofClasses에 모든 원소들의 클래스를 때려 넣어야 하는 것이었다.
let unarchivedArray = try! NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, CLLocation.self], from: data)
애플 개발자 포럼이 Stack overflow보다 도움이 된 흔치 않은 경험.
그런데, unarchiveTopLevelObjectWithData(_:) throws를 사용하면 클래스지정 없이도 멀쩡하게 unarchive된다. 그러나 쓰기 찜찜한 부분이… 알수없게도 iOS 12를 기준으로 NSKeyedArchiver.h 헤더를 보면 Objective-C에서는 +unarchiveTopLevelObjectWithData:error:가 deprecated 인데, Swift에서는 아니다? 오묘한 ObjC interoperability의 세계…
SyncScore는 2011년에 첫 출시한 클래식 음악 앱 시리즈이다. 이 유료앱들이 기적적으로 제법 팔려준 덕분에 진인웍스가 설립 초기에 홀딱 망하지 않고 버틸 수 있었다.
마지막 업데이트는 3년 전. 그런데 얼마 전 애플에서 메일이 하나 왔다(좋은 소식, 나쁜 소식, 관계없는 소식 가리지 않고 애플에서 메일이 오면 일단 앱 개발자의 심장은 살짝 덜컥 한다). 앱이 더 이상 앱 스토어 리뷰 가이드라인에 부합하지 않으니 확인하라고.
마지막 업데이트가 너무 오래 전이라서 앱스토어 청소의 대상이 된 것이다. 일한다 앱 리뷰팀! 침착하게 3년 만에 SyncScore 프로젝트를 열어서, 업데이트 하는 김에 11인치, 12.9인치 iPad Pro 화면 지원까지 넣어서 리뷰 신청을 하고, 무사히 업데이트 되었다.
우선, 너무나 느리다. 라즈베리 파이에 랜선으로 연결해도 최대 초당 8메가바이트 정도의 몹시 느긋한 백업 속도를 보여준다. 그런데 아무것도 안 하고 있는 맥을 한 시간 마다 백업할 때도 타임머신은 100-200메가바이트 정도를 디스크에 쓴다. (무엇을 쓰는 지는 짐작이 가지 않는다.) 여기에 굼벵이 속도가 결합하면 맥이 돌아가는 거의 모든 시간 동안 타임 머신 백업이 진행중이라는 어쩐지 마음이 편치 않은 일이 벌어진다.
게다가 라즈베리 파이가 열심히 디스크를 쓰는 동안 만약 네트워크 연결이 불안정해지면 곧바로 백업 데이타에 오염이 생길 수 있다. 실제로 몇 번인가 백업에 사용되는 Sparse bundle이 맛이 가서 전체 백업을 처음부터 다시 시작해보니 이 장치가 미워지기 시작했다.
결국 긴 케이블을 하나 구입하여 타임머신 하드디스크를 멀리 두는 방법으로 돌아왔다. 재미는 없지만 빠르고 조용하다.
iOSDevWeekly를 통해서 Hammerspoon이라는 오픈소스 앱을 알게 되었습니다. 이름이 재미난 이 앱은 맥에서 항상 돌아가면서 여러 이벤트나 키보드 단축키에 반응하여 Lua 코드로 작성된 기능들을 실행합니다. 설치하면 멋진 아이콘과 함께 맥 자동화의 방대한 가능성이 열리고 너무나 할 수 있는 것이 많아서 어디서부터 시작해야 하나 막막해집니다. 그 때 생각난 것이
AirPods. iOS와는 잘 붙고 잘 떨어지는데 어째서인지 맥과의 연결이 매끄럽지 못합니다. Toothfairy라는 앱을 써보았지만 별로 도움이 되지 않습니다. 가장 신뢰성 있게 맥과 에어팓을 연결하는 방법은 메뉴바 > 블루투스 아이콘 > AirPods > connect인데, 이거 차라리 유선 이어폰을 꼽고 말지, 라는 생각이 들어서 괴로워집니다. 블루투스는 사람들에게 고통을 주기 위해서 악마가 만들어낸 표준이 아닐까요?
Hammerspoon과 BluetoothConnector로 괴로움을 조금 줄일 수 있습니다. 단축키 하나에 hs.execute()로 BluetoothConnector가 동작하도록 지정해 두는 것이죠.
최후의 방어선 온라인 백업은 Backblaze입니다. 만약 집에 불이 나거나 역대급 천재지변으로 많은 것이 사라져도 작업을 부활시킬 수 있다고 생각하면 1년에 50달러는 아깝지 않습니다. 주로 쓰는 맥은 생각날 때마다 사나흘에 한 번, Super Duper!로 클론을 만들어 둡니다.
디스크 클론이나 온라인 백업은 사실 마음의 평화를 위하는 측면이 더 크고, 실제로 가장 꺼내쓰기 좋은 백업 방식은 역시 Time Machine입니다. 한 시간 단위의 자동 백업 덕분에 절망의 구렁텅이에서 건져 올려진 경험이 다들 한 번 쯤 있을 것입니다.
하지만 무선 주변장치의 시대에 USB 하드디스크를 직접 연결해야 하는 Time Machine은 우아하지 못합니다. 무선으로 우아한 Time Capsule은 출시가 너무나 오래 전이라서 붙어있는 공유기의 성능이 의심스러운 가운데, 급기야 단종되었습니다 (잠시 묵념). 와이파이에 연결된 하드디스크를 하나 만들어서 타임 캡슐 대용으로 써먹을 수 있지 않을까, 연구를 좀 해보았습니다.
Pime Capsule
연구 결과는 라즈베리 파이에 3TB 하드디스크를 연결하여 타임 캡슐로 쓰는 것입니다. 불현듯 Pime Capsule이라는 이름이 떠올라서 흡족했는데 검색을 해보니 역시 누군가가 먼저 생각해낸 이름입니다. 그러니 이 이름은 쓰지 않습니다.
DIY 타임 캡슐 만들기는 라즈베리 파이 3B 모델로 먼저 시작했습니다. 그러나 2.4GHz 와이파이로는 백업 속도가 너무 느려서 맥이 거의 항상 백업 중인 상태가 됩니다. 3B+에 랜선을 꽂아서 쓰면 2-3배 속도 향상이 있습니다. 만, 어라? 무선이 아니네? 음…
외장하드 불면증 해소
용건이 끝나면 바로 잠드는 맥에 직접 연결된 외장하드와 달리, 라즈베리 파이에 연결된 외장하드는 24시간 돌아갑니다. Caleb Wood가 제안한 hdparm은 도시바 하드에서 어쩐지 동작하지 않습니다. hd-idle이라는 프로그램도 시도해보았으나 소용 없습니다. 어지간히 잠자기 싫어하는 도시바 하드를 재우기 위해서 결국 스크립트를 하나 만듭니다.
sudo apt install hdparm sysstat python3
이것을 설치하면 iostat으로 외장하드의 읽기/쓰기 상태를 확인할 수 있습니다. 60초 주기로 iostat을 돌리고 읽기/쓰기 활동이 없으면 외장하드에 잠자기를 먹이는 파이썬 스크립트입니다.
import subprocess
def issue_standby():
subprocess.call("sudo hdparm -y /dev/sda", shell=True)
process = subprocess.Popen("iostat -y 60 sda", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while process.poll() is None:
outstring = process.stdout.readline()
if "sda" in str(outstring):
components = outstring.decode("utf-8").split()
byteread = int(components[4])
bytewritten = int(components[5])
if byteread + bytewritten == 0:
issue_standby()
/home/pi/hdsleep/hdsleep.py에 담고, crontab -e로 crontab에 아래 라인을 추가하여, 부팅하면 위 스크립트가 돌도록 해줍니다.
장대한 삽질을 거치면서 어째서 똑똑한 사람들은 간편한 문제 해결을 위해서 돈을 아끼지 않는지, 혹은 애플은 어째서 타임 캡슐을 접었는지에 대하여 희미한 힌트를 얻을 수 있었습니다.
수정
2018-06-14 netatalk 버전:
힘들게 직접 컴파일해서 최신 버전의 netatalk를 돌리니 알 수 없는 이유로 몇 번의 백업 이후에 자꾸만 파일 시스템 오류가 나면서 Disk Utility 조차 디스크를 포기해 버립니다. apt install netatalk로 설치하는 오래된 버전을 사용하니 오히려 문제가 안 발생하는데 운 때문인지는 잘 모르겠습니다.
Vapor 2를 이용하는 MoonBurst를 만든 것이 얼마 안 된 것 같은데 Vapor 3.0이 릴리즈 되었습니다. 근본적으로 많이 바뀌어서 Controller를 완전히 새로 작성해야 했습니다. 최근에 나온 멋진 것을 쓰려면 어느정도의 양계질은 피할 수 없나봅니다.
Vapor 3에는 SwiftNIO? Future? 생소한 것들이 많이 도입되어 단순한 블로그 엔진을 업데이트하는 데에도 상당히 애를 먹었습니다. 굉장히 강력한 기능을 가진 다목적 웹 후레임웤을 그저 라우팅하고 HTTPResponse 만들어 돌려주는 데에 쓰고 있자니 핵폭탄으로 파리를 잡는 기분이 들고 뭐 나쁘지 않습니다.
macOS에서 긴 웹툰 JPEG 파일을 짧게 잘라서 나눠주는, 웹툰 편집자의 필수 앱! … WebtoonCutter가 0.3으로 업데이트 되었습니다.
3월에 공개한 WebtoonCutter 0.2가 어째서 웹툰 세계에서 돌풍을 일으키지 못하였나? 라는 고민을 하다가, 그래 앱 아이콘이 없어서 그럴거야! 라는 (아마도 잘못된) 결론을 얻었습니다.
iOS 앱 아이콘은 Sketch로 만듭니다만, macOS 아이콘은 어떻게 만들면 좋을까요. Bjango 존잘님의 포스트를 보니 아이콘을 만들 때 3D 렌더링을 하기도 하는군요. 전에 구체적인 목표 없이 Blender를 배우다가 포기한 생각이 나서 이번에는 확고한 목적을 가지고 다시 배워보았습니다.
Blender는 참으로 맥스럽지 않은 UI를 갖고 있어서 아주 기초적인 사용법에 익숙해지는 데도 상당히 오래 걸립니다. 운좋게도, 맨땅에 Blender Tutorial로 검색하여 Blender Guru라는 훌륭한 튜토리얼을 발견하였습니다. 우선, 그가 손쉽게 도너츠나 모루를 만드는 것을 침을 흘리면서 구경했습니다. 그다음, 내가 만들면 왜 어딘가 이상할까 약간의 좌절을 거치면서 튜토리얼을 따라해 봅니다. 무수한 양계질 끝에 구상했던 작두 모양 아이콘을 만들었습니다. 좀 더 잘 만들고 싶지만 이미 비이성적으로 많은 시간을 아이콘 제작에 써버렸습니다. 모자란 아이콘이라도 없는 것보다는 낫겠지요.