들어가기에 앞서,
- 구매한 도서를 본인의 능력으로 직접 DRM을 해제한 경우는 국내법상 합법으로 알고 있습니다.
- 다른 사람이 도와주거나 대신 해제해준 경우 불법입니다.
- 구매가 아닌 대여 도서의 해제도 당연히 불법이겠죠?
이 글은 작업의 기록이지 세부 내용이나 방법은 다루지 않습니다.


솔직히 난 종이책이든 이북이든 책을 잘 읽지 않는다.
그렇지만, 우연히 지인의 이북 리더기를 보았는데 갖고 싶다는 생각이 들었다.
모델명은 물어보지 않았지만, 아마 오닉스 포크6 인 것 같은데,, 가볍고 생각보다 빠른 화면 전환...

오닉스 포크6 상품페이지에서 발췌, 보따리상 지들도 중국 소개 긁어오면서 템플릿 마냥 자기 상호 워터마크 밖은게 참 같잖다. 같은 이미지 워터마크만 다른 상품페이지가 한가득...

그래서 어떤 제품이 있을까 뒤적이다가, 뜬금없이 epub drm 에 꽂혔다.
이북리더를 산들, drm 에 묶인 전자책을 전용 뷰어앱으로만 보고 싶진 않았거든.

옛날 옛적에 리디북스의 DRM 해제에 대한 방법과 코드가 공개된 적이 있었다.
블로그 글은 사이트가 날아간 것 같고, 깃헙은 남아있네 - https://github.com/disjukr/ridi-drm-remover

 

GitHub - disjukr/ridi-drm-remover: https://www.bpak.org/blog/2018/04/%EB%A6%AC%EB%94%94%EB%B6%81%EC%8A%A4-%EC%9E%90%EC%8B%A0%EC%

https://www.bpak.org/blog/2018/04/%EB%A6%AC%EB%94%94%EB%B6%81%EC%8A%A4-%EC%9E%90%EC%8B%A0%EC%9D%B4-%EC%86%8C%EC%9C%A0%ED%95%9C-%EC%B1%85-drm-%ED%95%B4%EC%A0%9C%ED%95%98%EA%B8%B0-feat-%EC%9C%84%ED%9...

github.com

저분은 무슨 깡으로 저런걸 공개했는지 모르겠지만,, 나도 당시에 코드를 받아 한권 정도 재미로 풀어봤었다.
이게 생각이 나서, 나도 DRM을 풀어서 소장해야겠다.. 라는 이상한 의식의 흐름이랄까.
그래서 시작된, 이북리더 구매도 전에 EPUB DRM 부터 해제하기!

플랫폼마다 난이도는 천차만별일거라,, 일단은 EPUB 의 DRM 대해 공부해볼겸 만만하게 공공도서관의 전자책 대여 시스템을 타겟으로 삼았다. 물론 대여한 도서의 DRM 제거는 빼박 불법이겠지만,, DRM 기술에 대한 학습 목적으로...?
난 취미삼아 다른 서비스들 리버싱을 종종 해보는데, 그러면서 배우는게 꽤 많았던 것 같다.

그리고 미리 전자책 업체에 대한 변명을 해주자면,, 소프트웨어에서 창과 방패의 싸움에서는 절대적으로 창이 유리하다.
특히 전자책과 같이 소프트웨어와 컨텐츠가 사용자 기기로 내려받아지고, 로컬 기기에서 복호화를 해서 내용을 표시하는 경우엔 무조건 뚫릴 수 밖에 없다. (극단적으론 책 한장한장 다 사진 찍어서 만들면 그걸 어떻게 막을 것인가?)

그래서 기술 외적으로 사법의 힘을 빌려 처벌하거나, 최대한 귀찮게 만들어 효용 가치를 사라지게 하는 방법이 유효하다. OTT 서비스가 부상하면서 쉽게 VOD를 볼 수 있게 되자 웹하드나 토렌트가 상당히 죽은 것과 비슷하달까?
그러니 이렇게 뚫린다고 해서 그 기관이나 개발사를 괴롭히는 일은 없었으면 좋겠다..


가장 먼저 해볼 일은, 책을 구매하든 대출하든 전용 뷰어로 열어보는 일이다.
그래야 중간에서 컨텐츠를 훔치던가 복호화를 하던가 할 수 있으니까...
나쁜 업체, 하지만 내 입장에선 고마운 업체가 대충 만들면 중간 네트워크 패킷을 훔치는 것만으로 DRM 프리한 컨텐츠를 그대로 얻을 수 있을지도 모른다.

그러다보니, 훔쳐보지 못하게 하는 보안 기술 중에 SSL Pinning 이란 것도 있다. 이걸 적용하는 경우는 잘 없는데,, 좀 의외...
물론 창이 유리하다고 했지? 이런 것도 우회할 수 있다.
그래도 환경에 따라 우회가 상당히 제한되기도 하고 방법이 더 복잡해지기 때문에, 이런 조치만으로도 나같은 툴 키디의 공격으로부터 서비스를 보호할 수 있게 된다.
다른 OS에서 SSL pinning 을 우회활 방법을 만들어 뒀었기에 OS 를 바꿨는데,
다른 OS의 앱에선 pinning 이 안걸려 있어서 편하게 바로 스니핑이 가능했다.

뷰어 앱에서 전자책을 열람할 때마다 서버에서 받아오는 응답의 일부

얘들은 로그인 인증이 개판이라 이런건 좀 혼나도 될 것 같긴 한데,, 지금 중요한건 아니니 넘어가도록 하자.
주고 받는 패킷에 epub 주소가 있고, 그냥 다운받아 쓰고 있다. '어라? 그냥 저 epub 를 다운받아주기만 하면 되나?'

까비~
파일을 열어보니 신기하게 파일이 통째로 암호화된게 아니다. 뷰어에서도 책의 이름은 정상적으로 가져오더라.

EPUB 는 컨테이너로 zip 을 사용하고 있어서, 확장자만 zip 으로 바꾸면 압축파일처럼 내부 파일이 풀린다.
그 안에 책에 대한 정보나 이미지, 본문 텍스트 등이 있는 구조인데, 기본 정보 외에 폰트, 이미지, 텍스트는 모두 암호화된 상태였다.
도서를 한번 열람하면 앱 내부에 .epub 파일을 .zip 으로 저장하던데, 아쉽게도 암호화는 유지한 상태로 저장을 하고 있었다. 책을 열 때 마다 복호화하는 구조.

xml 구조인 메타 데이터 내에서 눈에 띄는 키워드로 검색해보니 EPUB 의 DRM 에 주로 사용되는 표준 암호화 방식이 있더라.

EPUB 파일 내부 META-INF/license.lcpl
EPUB/META-INF/encryption.xml

대충 정리하면 리소스는 각 aes 암호화가 되어 있으면서, 그 aes 키는 license 파일에 다른 키로 암호화된 상태로 저장되는 구조였다.
암호화 하는 다른 키는 구현하기 나름이겠지만, 내가 본 업체는 RSA 키로 암호화가 되어 있었다.

조금 특이한건, 서버에서 내려주는 epub 파일 자체는 키가 없어서 내가 복호화할 수 없는 license 파일이다. 대신 도서를 열 때 내 기기에 저장된 RSA 키 페어 인증서를 서버에 보내는데, 그 때 내 공개키로 암호화된 값을 가진 새 license 파일만 내려준다.
그러면 앱은 로컬에 저장된 epub 내의 license 파일을 서버에서 새로 내려준 파일로 덮어씌운채로 저장해둔다.

리소스 자체는 aes, 즉 대칭키로 암호화되어 있기 때문에, 라이선스 파일이 교체되지만, epub 리소스를 복호화 하기 위한 키 자체는 고정된 동일한 값이다. 어차피 한번만 복호화 키를 얻으면, 완전 복호화한채로 저장하면 되기 때문에 뭔들...


이제 남은건 2가지.
1. 뷰어 앱 혹은 기기에 저장된 개인키
2. 복호화 방식

개인키를 추출하는데 가장 오래걸렸는데,, 결국 내 기기에 설치된 뷰어 앱이 해당 개인키로 복호화를 하기 때문에, 어딘가엔 있다. 굳이 꺼낼 수 있는 방법이 마련되어 있지 않을 뿐. 어디에 있는지 모르기 때문에 찾는게 좀 귀찮다.
드물게 하드웨어에 키를 저장하고 사용하는 방식의 경우 키 추출이 불가한 경우도 있는데, 다행히 그렇진 않았다.(보통 잘 안쓴다.)
저장소 자체가 어디인지는 찾지 못한게 아쉽지만,,, 뷰어앱도 결국 복호화를 위해 비밀키를 메모리로 불러오기 때문에 그 과정에서 키를 가로채는게 가능하다.

내가 분석한 앱은 앱 자체는 하는 작업이 거의 없는 가벼운 앱이었고, DRM 프레임워크가 앱 용량의 대부분을 차지하고 있었다.
멀티플랫폼 지원용인지, OpenSSL 을 프레임워크 내에 내장하고 있더라.

시간이 조금 걸렸지만, 어쨋든 비밀키 추출 성공!
난 리버싱을 잘하는건 아니라서, 비효율적인 편법을 주로 사용한다. 상당한 노가다를 동반한다고 생각하면 됨.
위에서 서버에 보내는 내 기기 인증서의 키와 페어임도 확인했다.

RSA 비밀키는 e, p, q, d 로 구성된다. N은 p,q 로 만드는거고...

참고로 복호화는 키가 틀려도 일단 복호화 자체는 문제없이 된다. 결과물이 쓰레기일 뿐...
그래서 키가 올바른 키인지 검증 과정이 따로 필요한데, 라이센스 파일의 encryption.device_key.key_check 값을 비밀키로 복호화하면 license 파일의 id 와 동일한 값이 튀어나온다. 이를 통해 올바른 키인지 확인할 수 있다.

조금 특이한건, 암호화 과정에서 보통 PKCS7 패딩을 많이 쓰는데,, ISO 10126 패딩인 것 같더라?
리버싱으로 복호화를 할 땐 기껏 키를 찾고 나면 iv와 패딩을 찾는데도 꽤나 시간을 쓰기도 한다. 이번엔 다행히 쉽게 해결함.


그 후에 찾은 복호화 방식은 비교적 간단했다.
encryption.content_key.encrypted_value 를 내 개인키로 복호화하면 32바이트 키가 나오고, 이를 이용해 리소스 파일들을 각각 복호화하면 된다.
기술적으론 암호화 키를 여러개 사용할 수도 있나본데, 어차피 하나만 쓰고 있으니 그런 경우는 무시.
먼저 압축한 후 암호화된 경우도 있으나 이 정보도 xml 에 기록되어 있으니 적절히 리소스에 따라 복호화 및 압축 해제해주기...

복호화 방식을 알고 비밀키가 있으니, 나머지 과정은 자동화할 수 있다.
대여/구매한 책의 라이선스 키를 받고, 암호화된 epub 를 다운받은 뒤 복호화까지 한방에 하는 스크립트 작성이 가능하단 의미.

drm 해제가 완료되면 평범한 epub 파일이 된다. epub 포매을 지원하는 어떠한 앱(나의 경우엔 애플 도서 앱)으로도 볼 수 있다.

법의 보호를 받는 친구다. 나에겐 적법한 권리가 없는 저작물이고 학습 목적으로 잘 활용했으니 완전히 삭제해주도록 하자

 

근데 여긴 아쉽게도 내가 읽고 싶은 책이 없네.

그냥 poc 삼아, drm 구현의 공부 삼아 대출 도서로 진행해봤다.
실제론 이북으로 구매하고 그 구매한 책을 대상으로 사용할 예정인데, 업체마다 DRM의 구현 방법은 다 제각각이기 때문에, 완전 처음부터 다시 분석해야하고 방법이 완전 다를 가능성도 있다.
운 좋으면 같은 외주사가 개발했다거나,, 사용 기술이 비슷해서 날먹할 수도 있지만....

어쨋든 시도해보기 전엔 난이도를 알 수 없는데, 부디 내가 쉬운 이북 업체를 잘 찍기를 바라보며,,,
다른 서비스의 DRM 해제는 이북리더를 진짜 사게되면 진행해봐야겠다.


이북리더도 구매했겠다, 진짜 사용을 위해 다른 업체에서 진행해보았다.

얘들은 DRM 업체의 솔루션을 구매해서 사용하고 있던데, 이 시스템이 서버와 통신하는 방식이 조금 특이하다

처음 요청을 보내면 라이센스 서버의 인증서와 nonce 를 반환하고, 클라이언트는
1. random 16B 생성, key 로 사용
2. nonce+사용자아이디를 1번의 키로 암호화 = password
3. 1번의 키를 서버가 준 라이센스 서버의 인증서로 암호화 = KEK
4. 이를 서버로 다시 전송
하는 과정을 거친다.

아마 솔루션 자체가 HTTPS 가 대중화되지 않았던 과거에 만들어진 후 아직 그대로 사용하는게 아닐까 싶다.

또 이상한건, 위 업체처럼 기기의 비대칭키와 인증서를 생성하게 되는데, 이 키를 서버에서 생성해서 내려준다..ㅡㅡ;;
암호화된 pkcs8 로 내려주는데,, 이 암호도 요청에 포함이 되어 있고.............

비대칭키 쓸 때 대표적으로 하지 말라는 모델 아닌가,, CSR 같은건 왜 안쓰고.... (물론 위에 처음 뚫어본 업체도 그랬을 수도 있지만,, 확인해보지 않았다)
이런거 역시 옛날에 솔루션이 만들어졌다고 생각하면 클라이언트에서 키 만들기 어려웠던 시절도 있으니까, 일단은 그러려니......

여튼 그러고나면 얘들도 암호화된 .epub 파일과 license 파일을 내려받는다.
license 파일은 비슷한 듯 다른데, 어쨋든 기기의 비대칭키로 복호화할 수 있는 값을 가지고 있다.

얘들은 epub 자체도 자체 기술로 암호화되어 있고, 암호화를 풀면 내부 리소스 중 일부 데이터(주로 텍스트 위주)가 한번 더 암호화되어 있다. 겉 암호화는 풀어서 기기 내부에 저장하고 있으니, 이런 형태를 취했나 싶긴 한데.. 좀 이상하긴 함

암호화된 파일은 이렇게 생겼다. 파일 헤더에 html 스타일 주석이 있고 뒤에 바이너리 데이터 주루륵

아무튼 또다시 자세한건 생략. 이번엔 정당한 이용을 위해(?) 책을 구매하고, 구매한 책의 DRM을 해제하였다.

군대에 있을 때 병영 도서관에서 읽고 마음에 들어서 실물 책을 따로 구매했던 강세형 작가님의 [나는 다만, 조금 느릴 뿐이다]. 그 책보다 먼저 출간했던 책

 

DRM이 해제되었기에 구매한 이북리더에서의 기본 리더 앱에서도 잘 열린다.

이 고생을 한 자기 합리화일지도 모르겠지만, 역시 기본 내장앱이 더 빠릿하고 쓰기 편한 것 같다ㅋㅋㅋ

 

DRM 기술 담당은 아니지만 콘텐츠 플랫폼 회사에서 앱 개발하는 친구.. 나중에 심심하면 거기도 도전해볼게 헿

 

'프로젝트 모음' 카테고리의 다른 글

카테고리 프롤로그  (0) 2026.02.22

내가 프로그래밍을 하고 좋아하는 이유는, 내가 필요로 하고 상상하던 기능이 현실이 되어 나의 삶을 보조해주기 때문인 것 같다.
그리고 나의 경험상, 내가 필요로 하는건 분명히 또 다른 누군가도 원하고 있기 마련이더라.

혼자만 쓰다가 다른 사람도 같이 쓰기 위해선 고려할게 많아지고, 불특정 다수 또한 같이 쓰기 위해선 신경 쓸게 곱절로 늘어난다.
개중엔 리버싱의 결과로 탄생하거나 그게 메인이라 공개할 수 없는 작업물도 존재하고..

그래서 세상에 공개할 순 없는 프로젝트들이지만, 혼자만 알기엔 아까운 것도 너무 많단 말이지.
나 자신의 기록 겸 이런걸 만들 수도 있다는걸 소개할 겸, 하나씩 남겨볼까 한다.

'프로젝트 모음' 카테고리의 다른 글

전자책(EPUB) DRM 해제  (0) 2026.02.22

(무려 3년 만의 포스팅.. 요즘엔 그냥 개인 노션에 정리만 하는 편...)

어쩌다보니 식당 한군데의 자잘한 일들을 봐주고 있다.
주로 네트워크, CCTV나 매장에서 메뉴판으로 쓰는 아이패드 관리 따위인데,,
어제는 매니저님에게 전화가 오더니 네트워크 어쩌구 하시더라.

이야기를 들어보니, 포스에서 주문을 넣으면 주방 등에 영수증으로 주문 내역이 출력되는데, 그 출력이 안되더란 이야기.
그래서 포스사 직원을 불렀는데 직원은 네트워크 문제일거라고 하셔서 나에게 도움 요청하신거였다.

내가 한건 아니지만,, 매장 구조상 한 곳으로 보인 랜선을 랜 커플러로 다시 다른 곳으로 보내서 연결이 주렁주렁된 터라 찔리는게 있어서 그러려니 하였고,, 그래도 현상이 이상해서 매장을 방문해봤다.

서론이 꽤나 길 것 같은데...

[토스 결제 시스템에 대한 배경 지식]

토스 플레이스는 위 사진과 같은 조합의 SW/장비들을 제공한다.
왼쪽은 포스 프로그램. 포스 프로그램이란게 늘 그렇듯 필수는 아니다. 있으면 매장 주문 관리 등이 조금 편해지는거고...
윈도우, 맥, 아이패드, 갤럭시 모든 플랫폼을 지원하는게 특이한 포인트 + 보통 사용 수수료를 받는데 토스 포스는 무료로 사용할 수 있다.
토스 포스 프로그램에 대해서도 써볼려면 글이 따로 하나 나올 것 같아서 하략...

그 다음은 토스 프론트. 주로 결제 단말기로 많이 사용하며 간단하게 메뉴를 표시하거나 사용자 입장에서 키오스크 주문기처럼 사용할 수도 있다. 안드로이드 기기임. 영수증 프린터 기능은 없기 때문에 유무선으로 다른 장비를 연결해줘야 한다. 보통 정말 결제만 되는 카드 단말기를 하나 백업으로 구비하고, 그 단말기가 영수증 프린터를 겸하기 때문에 거기다 연결하는 경우가 많은 것 같다.

그 다음은 터미널인데, 이번에 문제 생기면서 직원이 임시로 갖다줘서 처음 봤다. 프론트를 고객 쪽에 향하게 하고, 터미널은 안쪽 직원이 금액 입력, 영수증 출력 등의 용도로 사용하는 느낌인 듯. 생긴걸 보니 결제 단말기로써의 역할도 할 것 같다.

[토스 포스와 영수증 프린터]

토스 포스는 한 매장에서 동시에 여러개 실행할 수 있는데, 매장 현황(테이블 정보, 주문 내역) 등은 공유하지만 연결되는 결제 단말기나 프린터 정보는 공유되지 않는다.
다만, 한쪽에서 주문했을 프린터가 연결된 다른 기기가 켜져있다면, 그 기기가 주문 정보를 받아서 대신 인쇄를 해준다. 그래서 여러 기기에 걸쳐 프린터를 연결해두면 안됨. (이걸 모른 채로 자꾸 지운 프린터로 인쇄가 되어서 한참을 헤맸음ㅠ)

실행 중인 토스 포스에서 영수증 프린터로 인쇄 요청을 보내는 방식이기 때문에, 포스가 실행되는 장비 기준으로 시리얼/USB 연결된 프린터나, 블루투스, 와이파이 프린터로 인쇄시킬 수 있고, 토스 프론트와 연결하고 나면 토스 프론트에 유선 연결된 프린터로 인쇄시키는 것도 가능하다.
또한 A 프린터는 영수증만, B 프린터는 주방에 주문 내역을, C 프린터는 다른 곳에서 특정 주문만을 출력하는 등 프린터별로 인쇄 종류를 나눌 수 있다.

토스 터미널이 아니라면 인쇄는 ESC/POS 프로토콜로 인쇄 요청을 보내게 된다.

이 매장에서는 토스 프론트에 유선 연결된 결제 단말기 하나, ESC/POS 유선 이더넷 영수증 프린터 3대, 그리고 ESC/POS WiFi 프린터 1대를 사용 중이다.

세우테크 SLK-TS400W, 와이파이 영수증 프린터

[문제 상황]

다시 매장으로 돌아와서, 영수증 프린터 한대의 파손과 다른 프린터가 모두 작동을 하지 않아서 토스 플레이스 대리점 직원을 부른 상황.
직원 입장에선 토스 프론트와 직접 연결된 결제 단말기는 인쇄가 되고, 네트워크로 연결된 영수증 프린터가 작동하지 않으니 네트워크 탓을 할 수 밖에 없었을터다.

나도 현상이 너무 이상해서 이런저런 테스트를 해봤는데, 아무리 봐도 네트워크 문제는 아니였다.
그러다 내가 ESC/POS 프로토콜로 프린터에 직접 명령을 전송할 경우 인쇄가 잘 되는걸 발견하였다!!!

임시로 가져오신 토스 터미널 2대는 인쇄가 잘 되었다. 토스 터미널은 ESC/POS 프로토콜이 아니라 별도 방식을 사용하는 듯.

대리점에서 오신 분도 계속 네트워크만 의심하시다가, 내가 테스트한걸 보시더니 토스 포스 앱의 문제임을 99% 확신하시고 영상을 찍어가셨다. 토스 포스 앱이 갑자기 문제 생겼으리라곤 누가 의심했을까...
다른 매장에선 별도로 안된다는 리폿이 없어서 더 네트워크만 의심했던건데, 왜 다른 매장 리폿이 없었는지는 의문이다. 그냥 그러려니 하고 쓰거나... 토스 포스를 그만큼 안쓰거나....??

(수정) 다음날 대리점 직원으로부터 전달받았는데 다른 매장은 잘 된다고 한다... 저 매장도 전체 기기에서 프린터를 삭제하고 다시 등록하니 또 인쇄가 되기도 하는걸 발견....

대리점 직원분도 예전과 다르게 최근에는 매장에 설치해주러 갔을 때 프린터 연결이 잘 안되어서 힘들었는데, 포스 프로그램 탓이었던 것인가?! 하면서 한탄하시더라..ㅠㅠ 그건 아마 다른 문제일 것 같긴 하지만 그냥 그러려니 했다.

여튼 당장 할 수 있는게 없으니 둘 다 퇴근.

 

[해결 과정]

집에 가는 길에 문득 토스 포스에서 인쇄 요청을 어떻게 잘 못 보내는지 궁금해졌다.
위에서 얘기했듯, 포스 프로그램을 별도로 실행시키면 매장 영업에 큰 영향 없이 이런저런 테스트를 할 수 있다.
로컬에 소켓 서버를 열고, 뭐라고 오는지 받아봤다.

테스트 출력을 누르면 '테스트 프린트' 라고만 출력이 되는데, 그 요청을 받아봤다.
ESC/POS 프로토콜은 검색해봤음. ESC 는 0x1B 를, GS 는 0x1D 를 의미한다.
요청 받은 패킷을 해석하면 아래와 같다.

1B 52 0D
	ESC R n: Select international characters
    	n 13: Korea
1D 21 11
	GS ! n: Select character size
    	0 <= n <= 255
C5 D7 BD BA C6 AE 20 C7 C1 B8 B0 C6 AE 0A
	'테스트 프린트\n'
1B 21 00
	ESC ! n: Batch specify print mode
1D 21 00
	GS ! n: Select character size
1B 61 00
	ESC a n: Position alignment
    	00 or 48: left
        01 or 49: center
        02 or 50: right
1D 42 00
	GS B n: Specify/cancel white/black inverted printing

0A 0A 0A 0A 0A 0A
	'\n\n\n\n\n\n'
1D 56 31
	GS V: Cut paper
    
(아래 4개 중복)
1B 21 00
1D 21 00
1B 61 00
1D 42 00
(----------)
1B 40
	ESC @: Initialize printer

 

소켓을 제대로 못 열거나, 실수로 프린터 주소를 하드 코딩 해뒀거나.. 따위를 의심했는데 그런건 아니였다.
그저 소켓 연결 수립 직후 보내야하는 Initialize printer 패킷 (0x1B 0x40)을, 가장 먼저 보내지 않고 가장 늦게 보내고 있었다...ㅡㅡ;;
(수정) 다른 곳도 많이 이렇게 하는거보니 이게 문제는 아닌 것 같다.

그래서 매장에 따로 오드로이드(라즈베리파이 같은거) 한대 꽂아두고, 파이썬 서버를 띄워서 1B 40 으로 시작하지 않는 인쇄 요청은 내가 붙여서 다시 프린터로 보내도록 간단하게 프로그램을 짜두고, 토스 포스 프린터 설정을 다 바꾸었다.

그러니 정상 작동!

코드는 오래 쓸게 아니니 대충 작성해서 돌렸다.

import socket
import binascii
import traceback
import os

PORT = int(os.environ.get('PORT', '9100'))
TARGET = '192.168.20.' + os.environ.get('TARGET', '250')

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('0.0.0.0', PORT))
sock.listen(10)

while True:
	conn, client_addr = sock.accept()
	print('connection from', client_addr)
	
	proxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	is_first = True

	try:
		proxy.connect((TARGET, 9100))
		while True:
			data = conn.recv(1024)
			
			if data:
				if is_first:
					is_first = False			
					if data[:2] != b'\x1b\x40':
						proxy.send(b'\x1b\x40')
				
				# print('received', binascii.hexlify(data, ' ').upper())
				proxy.send(data)
			else:
				# print('no more data from', client_addr)
				break
	except:
		traceback.print_exc()
	finally:
		try:
			conn.close()
			proxy.close()
		except: pass

 

[다시 문제]

인쇄가 잘 되어서 싱글벙글하고 있었는데,, 어느순간 또 먹통이 되는 문제가 발생..
토스 터미널은 여전히 잘 되고... 토스 포스 프로그램을 완전히 종료하고 나면 또 잘 된다.
영상을 올리고 싶은데... 매장명이 노출되는데 영상 편집은 귀찮으니 생략....

매장 직원들에게 물어보니 예전부터 자주 그랬다고.... 직원들은 맺힌게 많았는지 포스 안 만들던 회사가 만드니 너무 불안정하다고 아예 시스템을 갈아버렸으면 좋겠다고 얘기하시더라.... (하지만... 토스 프론트가 예쁜걸.... 매장 주문 내역을 가져갈 수 있는 API 같은 것도 전혀 없어서... 바꿔야 한다는 의견도 프론트 예쁘다 원툴로 버텼는걸....)

그동안 안되는게 네트워크 문제일 줄 알았는데 전부 토스 문제였다니... 허허....

가짜 프린터 서버를 띄워두고 토스 포스에서 인쇄 테스트를 걸어두었는데,
안될 때는 토스 포스에서 프린터로 소켓 연결 수립 조차 안한다. PC 버전 기준 토스 포스 코드를 까봤을 땐 딱히 의심가는 부분은 없는데... iOS 쪽 패킷 발송 코드에 버그가 있나 싶은데, 이건 당장 내가 어떻게 할 수 없으니 GG...
그냥 안되면 포스 앱을 한번 종료했다가 다시 실행해달라고만 전해뒀다.


방문하셨던 담당자분에겐 이 문제를 전달해드렸는데... 이게 과연 토스 포스 개발팀에게 전달이 될지.. 되면 언제 수정이 될지...
토스 포스 앱 자체는 업데이트가 되게 잦던데, 금방 해결되길 바라본다.

문제를 파악하고 해결하는 과정이 나름 재밌기도 했고,
누군가는 또 오작동이 자기 매장의 문제일거라 생각하고 고통받고 있을 것 같아서,, 혹시나 하는 마음에 짧게 남겨 보았음.


(추가 수정) 결국 프린터로 요청 자체를 보내지 않는 문제 때문에 사용을 거의 못하셨다고 한다.
그래서 오늘은 토스 터미널 요청을 역분석해서, 터미널로 등록 → ESC/POS 프린터로 전송하도록 수정하였다.

토스 터미널은 :14555 포트에 http 서버가 띄워져 있으며, 토스 포스 앱은 3개의 엔드포인트를 요청한다.
- GET /: 404 반환됨
- GET /sn: { "serialNumber": "..." } 반환. 아무 값이나 뱉어도 무관함
- POST /print: 인쇄 요청, { "data": "base64 encoded..", "count": 1 }

헤더로도 몇가지 오지만 일단 무시.

base64 인코딩 된 문자열은 ESC/POS 명령어셋과 거의 동일하지만, 지원 인코딩 셋이 좀 다르다.
코드 상으론 좀 더 다양하게 지원하는 것 같긴 한데, 일단 ESC R n 에서 n = 0x0D Korean 표준을 따르지 않고, 0x0F 를 사용, 이는 GB18030 셋으로 인코딩 된다. 중국어 코드 집합인데,, 한국어도 잘 됨.

조금 귀찮지만 GB18030 을 디코딩한 후, cp949 에 없는 캐릭터셋은 대충 ? 로 치환해준 뒤, cp949 로 보내면 된다.

이를 위해 ESC/POS 명령어셋 파싱이 필요한데, 일단 내가 확인한 바로는 아래 명령어셋만 사용함.
- ESC @, ESC R, ESC !, ESC a, ESC E
- GS !, GS B, GS V

추가로 따로 포트번호가 없기 때문에 여러 대의 프린터랑 연결하기가 어렵다.
서버에 IP 를 여러개 할당한 후, 요청의 Host 헤더를 보고 적절한 프린터로 연결되도록 수정함.
도메인 주소는 토스 포스 앱에서 올바른 주소로 인정해주지 않는다...ㅎㅎ;;

조금 더 코드가 길어졌지만... 일단 이렇게라도 잘 되길 바라며....

(작성 중...)

보통 메일 서버를 직접 구축하는 짓은 하지 말라고들 한다.
스팸/바이러스 메일을 걸러내는 작업, 내 메일이 스팸처리가 되지 않게 하기 위한 작업, 메일을 중단 없이 수신할 수 있도록 지원 등등...
물론 직접 구축하는 작업이 아무나 할 수 있는게 아니기도 하고.

그래서 개인 도메인을 이용한 메일을 사용하기 위해선 유/무료의 기업용 서비스를 이용하거나 메일건 같은 서비스를 이용한다.
무료는 거의 없어지는 추세에 다음 스마트워크나 메일건 정도 남아있지만 제약들이 좀 불편했고, 유료도 서비스 자체나 과금 정책 등도 마음에 안드는 구석들이 하나씩 있어서 어떻게 하면 좋을까 고민하던 중, 어느정도 손이 가지만 타협할만한 방법이 있는 것 같아서 사용 및 설정을 해보았다.

일반 메일 서버 구축을 하듯이 Postfix + Dovecot 을 설정하지만, 실제 외부와 통신은 AWS SES를 거쳐서 진행하는 방식으로 구성을 하였다.
참고로 이미 이런 형태로 제공하는 가이드 등이 존재하지만, 다양한 계정으로의 수신을 지원하지 않아서 그 부분 등을 고려하여 세팅을 진행하였다.


AWS SES

AWS Simple Email Service 라고, 메일의 송수신을 지원하는 서비스이다.
다만 일반적인 메일 서비스처럼 지원을 하는건 아니고 송/수신이 조금씩 다르다.

일반적인 메일 서비스처럼 바로 사용할 수 있는건 아니지만(그런 목적으론 유료의 AWS WorkMail이 있다)
송신 기준으로 메일 전송에 필요한 각종 서버 설정을 하지 않고 AWS의 신뢰도에 기댈 수 있고, 수신 기준으로도 AWS가 일단 안정적으로 받아주고 시작하는게 좋아보여서 사용하기로 하였다. (바이러스 검사나 스팸 체크도 해주지만 내가 후처리를 따로 해줘야 한다.)

사용 방식 / 비용

먼저 송신은 초당, 일당 전송 제한이 있으며
AWS API를 이용하여 전송(월 62,000건 무료 후 과금)을 하거나, AWS IAM 사용자를 이용한 SMTP로 전송이 가능하며,
1000건당 $0.1 및 용량 기준으로 GB당 $0.12 가 과급된다.

후자의 SMTP 방식을 이용하면 일반 메일 클라이언트에 등록해서 사용할 수 있긴 한데, IAM을 사용하기 때문에 사용자 아이디/비밀번호를 내가 설정할 수도 없고 권한 부여도 까다롭다.

수신은 정한 규칙에 따라 AWS 람다를 실행하거나, S3에 저장하거나, SNS 토픽을 발행하는 등의 방식으로 이루어진다.
이 또한 1000건당 $0.1 (1000건 무로) / 256KB 1 chunk 기준으로 chunk 1000건당 $0.09 과금된다.
작성일 기준으로 한국 리전은 수신을 지원하지 않아 us-east-1 에서 사용하였음.

AWS SES 설정

AWS 에서 메일 송신을 위해선
1. 도메인 혹은 메일 주소 인증
2. 도메인의 경우 DKIM 등 설정
3. 외부로 메일 전송을 위한 Sandbox 해제 요청
작업을 해야하고,

수신을 위해선 MX레코드 설정이 필요하다.
이런 부분은 다른 사람들이 쓴 글이나 AWS 가이드에도 설명이 잘 되어있으니 생략.

이 후 Rule set 설정, Lambda, S3, SNS 등 설정은 분량 관계상 별도 포스팅으로. (작성 후 링크 예정)


Postfix

SMTP 프로토콜을 이용하여 메일을 수발신 할때 사용하는 프로그램. MTA (Mail Transfer Agent) 의 한 종류이다.
예전엔 sendmail 이 사용되었으나 요즘엔 Postfix를 가장 많이 사용한다.

Postfix 기능들

이 프로그램이 하는 역할이 겉으로 보기엔 단순 메일 수발신이지만 자세히 보면 좀 다양하다. 적절히 필요한 것만 취하면 됨.

1. 서버 내에서 내/외부로 메일 전송시 처리
    -> 안 써서 필요없음

2. 외부 클라이언트에서 서버를 통하여 메일 전송 (SMTP Submission)
    -> AWS의 SMTP를 사용해도 되는데, 메일 주소(사용자)별 인증을 쓰고 싶어서 이용.

3. 외부 클라이언트로 전송할때 사용자 인증 / 메일 검증 등
    -> 인증은 Postfix 자체적으로 할 수 있게 설정도 가능한데, 후술할 Dovecot과 연결하여 Dovecot의 인증을 사용하는 방법도 가능하다. 다 따로 구현하면 관리가 번거로워지니 이 방법을 사용할 예정.

4. 내 도메인으로 전송된 메일을 수신
    -> 나의 경우 수신을 AWS가 해주기 때문에 불필요한 기능이지만, 발송 과정에서 Postfix를 사용하고 있고 내 도메인으로 전송하는 경우가 발생할 수 있기 때문에 적절한 설정을 해줘야한다. (예시로 a@naver.com 에서 b@naver.com 으로 메일을 보내는 경우.)
        기본적으론 서버 내 해당하는 경로에 메일 원문을 저장하게 되어 있지만, 난 Dovecot 으로 전송하도록 설정할 예정

5. 메일 수신시 사용자 존재 여부 확인 / alias (다른 주소로 수신하는 기능)
    -> 메일 수신자에 따라 실질적으로 어떤 계정이 해당 메일을 받을지 결정하는 기능..
        support@도메인 으로 전송하면 CS팀에 속한 직원이 모두 해당 메일을 받는다거나.. 하는 목적으로 사용된다.
        난 내 도메인으로 오는 전체 메일을 내가 사용할 한두개 계정으로 받기 위해 사용할건데, 메일 수신을 Postfix만 하는게 아니라서 Postfix 와 Lambda 양쪽에 동일하게 작동하도록 구성해주어야 한다.

6. 다른 서버가 전송하는 메일을 다른 서버로 릴레이
    -> 보안 상의 이유 등으로 보통 비활성화

 

Postfix 설정

분량 관계상 포스팅을 분리하여 작성 (작성 후 링크 예정)


Dovecot

메일 박스를 관리하며 POP3, IMAP 등 프로토콜을 지원하여 다른 프로그램에서 메일을 받아갈 수 있도록 지원한다.
SMTP Submission 기능도 지원하는 것 같긴 하던데, 어차피 Postfix도 필요할 것 같아서 사용하지 않았음.

MTA(Postfix)가 메일을 수신해서 폴더에 저장하면 Dovecot이 알아서 그걸 읽는 방법도 있고,
MTA가 다시 Dovecot으로 LDA(Local Delivery Agent)나 LMTP(SMTP 개량 프로토콜)를 통해 보내줄 수도 있다.
예전엔 LDA를 썼었는데 최근엔 설정 편의성과 성능 때문에 LMTP를 더 많이 쓴다고 한다.

나의 경우 역시 Postfix - LMTP -> Dovecot 로 설정을 하였고,
AWS SES에서 수신한 메일도 AWS SES -> S3 -> SNS -> Lambda - LMTP -> Dovecot 으로 구성을 하였다.
보통 Lambda에서 Dovecot의 메일박스로 파일을 바로 전송시키는 방법을 사용하는데, 이러면 폴더 구조 같은걸 내가 신경 써줘야해서 난 수신자/alias 구분만 Lambda에서 하고 LMTP로 Dovecot에 떠넘겼음.

Dovecot 설정

역시 분량 관계상 포스팅 분리하여 작성 (작성 후 링크 예정)


사용 포트 / TLS

Dovecot 에서 사용할 POP3 와 IMAP 은 TLS 암호화가 추가된 버전인 POP3S(995/tcp), IMAPS(993/tcp) 만 사용할 예정이다.
내부 메일 전달을 위한 LMTP는 Postfix만 쓸거면 unix socket으로 해도 되지만, Lambda에서 사용해야 하니 적당히 임의로 2525/tcp 포트도 추가로 사용.

사용자가 메일 전송을 위해 Postfix에 접속할 때는 SMTPS(tcp/465) 대신 SMTP + STARTTLS 을 사용할거고, 25/tcp 가 OUTBOUND로 차단되는 경우가 많기 때문에, 587/tcp 포트를 사용한다.

즉 587, 993, 995 3개 포트는 모두 열려있어야 하며, 2525 포트는 내부에서 사용할 수 있게만 열려있으면 된다.

TLS 연결을 위해 인증서를 발급받아야 하는데, Let's encrypt(certbot) 을 이용한 웹서버용 인증서를 그대로 사용할 수 있다.
이 인증서의 발급 방법은 생략.
발급된 경로만 잘 확인하여 후에 설정할 때 적절한 경로를 넣어주면 된다.

이 후 발급된 인증서를 Dovecot / Postfix 각각 설정에 적절히 넣어주면 된다.


전체 구성도

아직 시작도 안했지만 벌써부터 복잡한, 전체 구성도는 아래와 같다.

DB와 메일 서버는 동일 서버여도 되지만 난 분리되어 있기 때문에 이미지상 분리해뒀다.

이미지에서 볼 수 있다시피 Lambda가 DB와 EC2 서버에 각각 접근해야 하는데, DB는 당연하고 LMTP도 Public 오픈을 권장하지 않기 때문에 Lambda를 private subnet에 넣고 동일 VPC 네트워크 내에 사설망을 이용하도록 구성하였음.


나중에 사용자 인증을 LDAP 으로 사용한다거나.. 등도 고민 중인데..... 갈길이 멀다.....ㅠ

iDRAC 에 접속하면 Virtual Console 이라고,, 간단하게 말해서 원격으로 서버 콘솔에 접근할 수 있다.
일종의 원격제어...
서버가 죽어도 서버실에 갈 필요 없이 왠만한 복구는 다 할 수 있게 해주는 한줄기 빛같은 존재

Java Web Start 라고, 웹브라우저에서 바로 자바를 실행할 수 있게 만들어진 프레임워크를 사용하는데, 요즘에 보안때문에 그런게 될일은 당근 없고, 그냥 viewer.jnlp 파일이 다운받아지는걸 자바로 실행해주면 된다.

평소엔 쓸 일이 없으니 봉인해두다가, 오늘 쓸일이 있어서 열어봤는데

안된다...ㅠㅠㅠ

검색해보니 Java Web Start 가 Java 9에서 deprecated 되었고, Java 11에서 삭제되었다고 함.
웹에서 실행해줄 뿐이지 어쨋든 자바니까, 직접 실행할 수 있는 방법을 찾아봤다.

우선 jnlp 파일을 열어보면 연결에 필요한 인자값들과 OS와 아키텍쳐에 따른 java 파일 링크가 걸려있다.
avctKVM.jar 파일과 연결하려는 시스템에 맞는 lib 파일들을 받는다.

그리고 구버전 JRE를 구한다. 설치할 필요는 없으니 오라클 홈페이지에서 tar.gz 버전으로 다운로드 받으면 됨.
1.7.0_80 (7u80) 버전이 된다길래 난 이걸로 했음.
bin, lib 등이 있는 폴더를(Mac버전 기준 Home) jre 라는 이름으로 바꿔서 가져온다.

정리하면,
┣ avctKVM.jar
┣ lib/
┃    ┣avctKVMIOMac64.jar
┣    ┗avctVMAPI_DLLMac64.jar
┣ jre/
    ┣ bin/...
┗    ┗ lib/...
이런느낌의 디렉토리 구성이 되도록..

이 후 viewer.jnlp 파일의 인자를 참고하여 다음과 같이 명령어를 실행하면 된다.
- viewer.jnlp 에는 일회용 ID/PW가 있는데, 그걸 써도 되지만 원래 계정을 입력해도 접속이 잘 됨.
- 없어도 되는 인자도 있겠지만 난 그냥 다 넣어줌.
- vmprivilege는 참고한 글에서 넣어줬길래 그냥 넣어줌.

./jre/bin/java -cp avctKVM.jar \
	-Djava.library.path=./lib com.avocent.idrac.kvm.Main \
	ip=SERVER_HOST \
	vm=1 \
	user=USER_ID \
	passwd=USER_PW \
	kmport=9595 \
	vport=9595 \
	apcp=1 \
	reconnect=1 \
	chat=1 \
	F1=1 \
	custom=0 \
	scaling=15 \
	minwinheight=100 \
	minwinwidth=100 \
	videoborder=0 \
	version=2 \
	vmprivilege=true

아, java 를 인터넷에서 받았다고 실행을 안해줄텐데, 파인더에서 option+열기로 먼저 실행해주거나,,, 여러가지 방법으로 허용해주면 됨.
난 걍 개인 정보 보호 - 개발자 도구 옵션에서 터미널을 추가했음

이러면 원래 쓰던거처럼 연결이 잘 됨.
다만 맥 기준,
한번이라도 한글인 상태로 타이핑을 하면 키 입력이 이상하게 들어가서 재접속을 해야하고
macOS 보안 옵션 문제인지 전체 키스트로크를 받을 수 없다고 오류가 난다. 이건 설정에서 직접 허용해주면 될 것 같기도 함.

 

zigbee2mqtt 설치

(이전 글에 합쳐져있던 내용을 새 글로 분리)

zigbee2mqtt (이하 z2m) 은 mqtt 란 프로토콜을 기반으로 작동하는건데,
z2m은 mqtt 클라이언트로서 연결할 mqtt broker (서버라고 생각하면 됨)가 필요하다.

docker 로 mqtt broker 인 eclipse-mosquitto 와 z2m 둘 다 설치할거임.

# eclipse-mosquitto 이미지 다운 및 실행
docker pull eclipse-mosquitto
sudo docker run -d \
    --name=mosquitto \
    -p 1883:1883 \
    -p 9001:9001 \
    --restart=always \
    -v /home/pi/ha/mosquitto/data:/mosquitto/data \
    -v /home/pi/ha/mosquitto/log:/mosquitto/log \
    eclipse-mosquitto

# z2m 이미지 다운 및 실행
docker pull koenkk/zigbee2mqtt
docker run -d \
    --name="z2m" \
    --net=host \
    --restart=always \
    -v /home/pi/ha/z2m:/app/data \
    --device=/dev/ttyACM0 \
    -v /run/udev:/run/udev:ro \
    -e TZ=Asia/Seoul \
    --privileged=true \
    koenkk/zigbee2mqtt


설정 파일 경로는 이전글에 이어서 /home/pi/ha/ 폴더 안에 각 폴더를 만들고 지정해주었다.

z2m 실행시 필요한 --device 이름 역시 필요하다면 알아서 바꿔주자.


z2m 의 설정을 조금 변경해준다.

기본값으로 꺼져있는 homeassistant 를 켜주고, devices, group 을 관리하기 편하게 별도 파일로 만든다

cd ~/ha/z2m
sudo touch devices.yaml groups.yaml
sudo vi /home/pi/ha/z2m/configuration.yaml
homeassistant: true
permit_join: true
mqtt:
  base_topic: zigbee2mqtt
  server: 'mqtt://localhost'
serial:
  port: /dev/ttyACM0
devices: devices.yaml
groups: groups.yaml

zigbee 장비가 다 추가되고 나면 permit_join: false 로 바꿔주면 됨.


그 다음 HA의 설정도 변경, mqtt 항목을 추가해준다.

mqtt broker 를 추가하는거기 때문에 혹시나 eclipse-mosquitto 를 다른 장비나 다른 포트에서 작동시킬 경우 적당히 수정해주자.

sudo vi ~/ha/config/configuration.yaml
mqtt:
  broker: 127.0.0.1
  discovery: true


둘 다 재시작

docker restart z2m home-assistant


이제 HA의 통합 구성요소에 MQTT 가 나타나고 기기와 구성요소에도 z2m을 통해 연결된 장비가 나타단다.

deCONZ 로는 안 보였던 다원 스마트 플러그의 전력량도 잘 보이고, 다원 전등 스위치도 연결이 잘 됨.


실시간으로 올라오는 z2m의 로그를 보려면

tail -f $(ls -td ~/ha/z2m/log/*/ | head -1)log.txt

이렇게 하면 됨. z2m을 재시작할 때마다 실행 시간으로 디렉토리가 생긴다.

tail 명령어로 열었는데, 지난 로그를 보고 싶으면 vim이나 nano 등으로 직접 열어봐도 됨.

단점/불편한 점이 많아서 사용하지 않는 중.

기본 스토로지를 사용하되 s3에 백업하는 방식으로 구성이 더 나을 것 같다.


NextCloud 는 기본적으로 시스템의 디렉토리를 사용하지만,

추가로 External Storage Support 기능을 사용해서 FTP, S3 등등 외부 저장소를 마운트하여 사용할 수 있다.

 

처음엔 이렇게 연결하려고 했는데, 뭔가 안되어서 찾다보니, S3자체를 NextCloud의 주 저장소로 사용하는 방법이 있어서 그냥 그걸 사용해보기로 했음.

참고로 지금 하려는 주 저장소가 아니라 외부저장소로써 S3를 사용할 경우

(1) 사용자마다 S3 연결을 직접하지 못하고 관리자가 해줘야함

(2) 저장공간 사용 제약을 할 수 없음

(3) NextCloud의 공유 기능을 사용할 수 없음

요런 3가지 단점이 있다고 한다.

 

참고로 Amazon S3 뿐만 아니라 S3와 비슷한 object storage 류들도 사용이 가능하다고 함.

또한 S3와 연결된다고 해서 S3에서 직접 파일 브라우징을 할 순 없다고 한다. 파일 자체는 있지만 메타데이터가 없어서...

기존 설치된 NextCloud를 옮길 수도 없다. 새로 설치해야 됨.

 

nextcloud/config/config.php 파일 설정에

  'objectstore' => array(
    'class' => '\\OC\\Files\\ObjectStore\\S3',
    'arguments' => array(
      'bucket' => '버킷이름',
      'autocreate' => true,
      'key'    => 'IAM Access Key',
      'secret' => 'IAM Secret',
      'use_ssl' => true,
      'region' => 'AWS 리전, 서울이면 ap-northeast-2'
    ),
  ),

요 부분을 추가해두고 설치를 진행하면 된다.

 

이미 진행된 인스턴스가 있을 경우 nextcloud/data 폴더와 DB 테이블들을 날려버리고, config.php에 installed: true 라인 지우고, 마지막으로 config 폴더에 CAN_INSTALL 파일을 생성하고 접속하면 다시 설정부터 진행할 수 있다.

 

S3를 주 저장소로 할 경우 php의 파일 업로드 제한에 영향을 받지 않는 것 같다. 아마 S3 API 로 바로 업로드 시키는 듯?

 

참고: Configuring Object Storage as Primary Storage - https://docs.nextcloud.com/server/15/admin_manual/configuration_files/primary_storage.html

 

+ File lock 비활성화

NextCloud 는 공유된 파일을 2명 이상의 사용자가 동시에 편집해서 꼬이는 문제를 방지하기 위해 File locking 기능을 사용한다.

DB, 혹은 설정된 mem cache 에 lock 정보를 가지고 있고, 30분마다 cron이 지워주는 방식으로 작동한다는데, S3를 사용할 경우 끄는걸 권장한다는 듯? ( https://autoize.com/s3-compatible-storage-for-nextcloud/ )

 

일단 당장 내가 lock 으로 인한 문제를 발견해서(생성되고 파일을 빠르게 지울경우 뭔가 이상하게 lock 이 걸려서 파일 삭제가 안됨..) 그냥 꺼버렸음.

 

config.php에

  'filelocking.enabled' => false,

를 추가해주면 된다.

Docker로 설치해봤는데, docker 이미지는 web installer 방식이라 초기 설치시간이 오래걸리길래 그냥 네이티브로 깔았다.

참고로 아래 설치법은 일부 선택 옵션들을 포함하니 참고.

Installation on Linux &mdash; Nextcloud 여기 잘 설명되어 있으니 그냥 여길 봐도 된다. 좀 귀찮게 해서 그렇지...


1. apache2 + php7.4 설치

Ubuntu 18.04의 기본 php 는 7.0이다. 7.3 이상을 권장하니 7.4를 깔아주자

nginx도 못쓰는건 아닌데,, 왠지 apache2 가 기본값인 것 같아서 그냥 apache2 로 진행하였다.

sudo apt -y install software-properties-common
sudo add-apt-repository ppa:ondrej/php

sudo apt install apache2 libapache2-mod-php7.4 php7.4 php7.4-bcmath php7.4-bz2 \
    php7.4-curl php7.4-gd php7.4-gmp php7.4-intl php7.4-json php7.4-mbstring \
    php7.4-mysql php7.4-xml php7.4-zip libxml2


NextCloud 에서 imagic 모듈도 설치를 추천한다.

sudo apt install php-imagick


2. NextCloud 설치

php 기반의 프로그램이다. 웹사이트에서 받아서 적절한 위치에 압축을 풀어만 주면 됨.

https://nextcloud.com/install/#instructions-server


3. apache2 설정

/etc/apache2/site-available/cloud.conf (당연히 이름과 각종 경로는 알아서 수정..)

<VirtualHost *:80>
        ServerName 사이트주소

        DocumentRoot /var/www/nextcloud/
        <Directory /var/www/nextcloud/>
                Require all granted
                AllowOverride All
                Options FollowSymLinks MultiViews
        </Directory>

        LogLevel emerg
</VirtualHost>

apache2 에 필요/선택 모듈을 활성화해준다.

a2enmod rewrite headers env dir mime


4. php 설정

php 기본 설정은 업로드 용량이 2MB이므로, 조금 늘려준다.

메모리 제한값도 기본은 128MB이지만 추천은 512MB

sudo vi /etc/php/7.4/apache2/php.ini
memory_limit = 512M
post_max_size = 10G
upload_max_filesize = 10G


5. HTTPS 설정

이건 알아서 해주자.

zigbee IoT 장치들(End Device)를 사용하기 위해선 장치들이 연결되기 위한 Coordinator가 필요하다.

코디네이터를 중심으로 end device 들이 연결되는데, 중간에 네트워크 확장을 위해서 라우터를 추가하기도 함.

코디네이터 혹은 라우터로 사용할 수 있는 CC2531 을 구매 후 펌웨어 변경 작업을 정리해 보았음.


CC2531 펌웨어 플래싱 with RPi

* 참고: 
    * What do I need? | zigbee2mqtt.io - https://www.zigbee2mqtt.io/getting_started/what_do_i_need.html
    * Flashing the firmware on the CC2531 USB stick | zigbee2mqtt.io - https://www.zigbee2mqtt.io/getting_started/flashing_the_cc2531.html
    * Alternative flashing methods | zigbee2mqtt.io - https://www.zigbee2mqtt.io/information/alternative_flashing_methods.html
    * ConBee implementation #72 - https://github.com/jmichault/flash_cc2531/issues/9


우선 CC2531(약 $5) 이랑 다운로더 케이블(약 $2)를 준비한다. 난 AliExpress 에서 주문하였음.

칩안테나(회로에 안테나가 그려져 있는) 버전이 있고 안테나 단자가 달려 있고 안테나를 같이 파는 버전도 있다.

CC2531 을 그냥 쓸 경우 신호가 약하다는 글을 많이 봐서 난 안테나 버전으로 구매하였음.


펌웨어 작업이 이미 되어 있는 CC2531을 팔기도 한다던데 그걸 살 경우 다운로더 케이블과 이 작업이 필요없다.


위에 참고로 건 글 중 Flashing the firmware on the CC2531 USB stick 글을 보면 Windows, Mac, Linux 에서 플래싱을 할 수 있지만, 왠지 pi에서 하는게 더 편할 것 같았다.

pi나 아두이노로 진행하는 방법은 Alternative flashing methods 글을 참고하면 됨.


1. 파이에 WiringPI 설치

sudo apt install wiringpi


2. 파이에서 툴 다운로드

git clone https://github.com/jmichault/flash_cc2531.git


3. 파이와 다운로더 모듈 간에 GPIO핀 연결

연결을 위해선 암-암 점퍼 케이블 4개가 필요함.

다운로더 모듈

 파이

 pin 1 (GND)

 pin 39 (GND)

 pin 7 (reset)

 pin 35 (GPIO24, BCM19)

 pin 3 (DC)

 pin 36 (GPIO27, BCM16)

 pin 4 (DD)

 pin 38 (GPIO28, BCM20)

다운로더 모듈의 핀보드는 모듈에 적혀있으니 그걸 보면 되고, 파이의 핀 위치는 https://pinout.xyz/ 사이트 참고.


4개 핀도 연결하고 전원 공급을 위해 CC2531 을 파이의 USB단자에도 연결해준다.


4. 플래싱

1) 먼저 잘 연결이 되었는지 확인해본다.

cd flash_cc2531
./cc_chipid -m 90
  ID = b524.

ID가 0000, ffff 등이 나온다면 뭔가 오류가 있음.

원래 -m 90 을 붙여주지 않아도 되는데, 나의 경우 계속 붙여줘야 정상적으로 진행이 가능했다.


2) 펌웨어 파일 다운로드

코디네이터 버전 - https://github.com/Koenkk/Z-Stack-firmware/tree/master/coordinator/Z-Stack_Home_1.2/bin/default

라우터 버전 - https://github.com/Koenkk/Z-Stack-firmware/tree/master/router/CC2531/bin

# 코디네이터
wget https://github.com/Koenkk/Z-Stack-firmware/raw/master/coordinator/Z-Stack_Home_1.2/bin/default/CC2531_DEFAULT_20190608.zip
unzip CC2531_DEFAULT_20190608.zip

# 라우터
wget https://github.com/Koenkk/Z-Stack-firmware/raw/master/router/CC2531/bin/CC2531_router_2020_09_29.zip
unzip CC2531_router_2020_09_29.zip


라우터 버전은 펌웨어가 3개가 나온다.

- router-cc2531-std.hex: 라우터 기능만 작동
- router-cc2531-diag.hex: 라우터 + 진단 정보
- router-cc2531-diag-usb.hex: 라우터 + 진당전보 + USB 시리얼포트로 인식 (진단정보를 USB로 읽기 가능)

진단정보가 뭘 말하는건지 모르겠어서 난 그냥 -std 버전을 사용하였음.


3) 펌웨어 플래싱

./cc_erase -m 90
./cc_write -m 90 CC2531ZNP-Prod.hex (혹은 해당하는 펌웨어명)


보통 HA에서 Zigbee 를 사용하기 위해 CC2531 칩 + zigbee2mqtt 를 많이 사용하지만,
ConBee II 라는 애가 "Very powerful"⁽¹⁾ 라고 소개되어 있어서가 있어서 구매해보았다.

아마존에서 $47에 구매, 57,304원이 빠져나갔다. CC2531 한개가 약 $5 인걸 고려하면 무시무시한 금액...ㅠㅠ


zigbee2mqtt 와 아직 호환이 완벽하지 않다고 적혀있어서⁽²⁾ deCONZ 란걸 사용해보았음

(1) Supported adapters | zigbee2mqtt.io - https://www.zigbee2mqtt.io/information/supported_adapters.html
(2) ConBee implementation - #72 https://github.com/Koenkk/zigbee-herdsman/issues/72


deCONZ 설정

# USB 제어를 하기 위해 현재 사용자를 dialout 그룹에 추가
# RPi 에서 pi 계정의 경우 이미 추가가 되어 있음.
sudo usermod -a -G dialout $USER

# 이미지 받아오기
docker pull marthoc/deconz

docker run -d \
    --name=deconz \
    --net=host \
    --restart=always \
    -v /home/pi/ha/deconz:/root/.local/share/dresden-elektronik/deCONZ \
    --device=/dev/ttyACM0 \
    -v /etc/localtime:/etc/localtime:ro \
    -e DECONZ_WEB_PORT=2000 \
    -e DECONZ_WS_PORT=2001 \
    marthoc/deconz


설정 파일 위치는 지정하기 나름. 이전 글에 이어서 /home/pi/ha/deconz으로 지정하였음.

--device=/dev/ttyACM0 부분은,, zigbee 모듈을 연결하고 해당 USB 항목을 넘겨주는건데 알아서 하자.

포트는 기본이 80, 443 인데 마음에 안들어서 각 2000, 2001 로 바꾸었다.

이제 http://RPi주소:2000로 접속 가능.


HA 웹페이지 통합 구성요소로 가면 deCONZ가 나타나고 클릭해서 연결하면 된다.

deCONZ 웹에서도 그룹을 만들거나, 제어를 하는게 가능하지만 어차피 HA로 제어를 할테니 zigbee 장비 연결만하고 돌아오면 됨.

그룹을 만들 경우 HA에도 나타나서 거슬린다.


문제점

 localhost 주소가 아닌 경우 pi의 IP가 바뀌면 HA와 deCONZ가 서로 통신을 못하는 어처구니 없는 일이 발생한다.

로컬주소로 바꿔봤는데도 이상함.. 그냥 deCONZ 자체가 IP가 바뀌면 이상하게 작동을 한다..ㅡㅡ


그리고 다원 DNS의 Zigbee Smart plug 는 연결은 되지만 제어만 되고 전력 소모량 등의 값은 올라오지 않는다.

Zigbee 전등 스위치는 아예 연결도 안됨...


결국 deCONZ 대신 zigbee2mqtt 를 다시 깔았다.. 이건 따로 글을 쓸 예정.

deCONZ 를 안쓸 경우 만들었던 docker 이미지를 꺼주거나, 지워주자.


# 인스턴스 정지
docker stop deconz

# 컨테이너 삭제
docker rm deconz

# 이미지 삭제
docker rmi marthoc/deconz


+ Recent posts