Comment_providers

Jekyll은 정적 사이트를 만드는 도구다. 반면 댓글 기능은 동적인 기능이니, Jekyll에서 구현하는 것은 원칙적으로 힘든 일이다.

Jekyll_hompage

Jekyll 홈페이지에서는 아예 댓글관리가 필요없다는 점을 장점으로 내세우고 있다.

이를 해결하기 위해서는 크게 두 가지 방법이 있는데, 하나는 외부 사이트를 이용해서, 댓글은 외부 사이트에서 전적으로 관리하고 이를 블로그에 미러링하는 방식이고 다른 하나는 댓글이 달리면 해당 댓글을 내 사이트 소스에 추가해서 보여주는 것이다.

_config.yml 파일을 열어보면, comments 항목의 provider에 다양한 댓글 제공 방식이 있는데, 이 중 disqus, facebook 등이 전자의 방식이고, staticman, utterances 등이 후자의 방식이다.

나는 둘째 방식이 여러가지 측면에서 합리적이라 생각하여 두 번째 방법을 사용했다. 내 블로그가 개발자 블로그였다면 망설이지 않고 utterance를 택했을 것이나, 댓글을 달기 위해 github 계정이 꼭 필요하다는 점, 그리고 내 블로그에 들르는 분들 중 많은 분들이 개발자가 아닐 것이라는 점(따라서 github 계정이 없을 것이라는 점)에서 나는 staticman을 이용하기로 했다.

2022년 11월부로 Heroku에서 제공하던 무료계정이 없어졌다. 변경 후엔 한 달에 5달러 정도에 가장 저렴한 플랜을 이용할 수 있는데, disqus의 plus는 한 달에 11달러라 그냥 disqus로 갈아타기로 했다.

Staticman의 작동원리

우선 staticman을 이용하여 댓글 기능을 추가하려면, 이 기능이 어떻게 돌아가는지를 아는 것이 도움이 된다.

  1. 방문자가 페이지 하단의 댓글란을 입력한 후 전송버튼을 누르면 staticman에 댓글 내용이 전송된다.
  2. Staticman이 이 댓글의 내용을 .yml 형식으로 저장하여, 사이트 저장소에 pull request를 보낸다.
  3. PR을 받으면 .yml 파일이 내 사이트 저장소에 속하는 파일 중 하나가 된다.
  4. 이렇게 모여있는 .yml 파일들 가운데에서, 지금 읽고 있는 포스트에 대한 댓글만 골라내어 페이지 하단에서 보여주면 된다.

나는 댓글에 답글을 달 수 있도록 하고 싶어서 이 또한 해결해야 했다. 또, 댓글 기능은 로컬에서는 보이지 않기 때문에 커밋을 하지 않고는 잘 작동하는지 그렇지 아닌지를 판단하기가 힘들다. 그래서 이 부분이 커밋 히스토리를 가장 난잡하게 만든 주범이었다.

봇 계정 만들기

우선 위의 작동원리에서 2번 과정을 보자. 내 블로그 저장소에 pull request를 보낼 수 있는 사람은 나 뿐이므로, 거꾸로 말하면 이를 위해서는 staticman에 내 github 계정 정보를 넘겨주어야 한다는 말과 같다.

이는 권장할만한 일이 아니므로 우리는 github에 봇 계정을 만든다. 구글 계정을 만들 때는 본인인증이 필요하지 않으므로, 이를 통해 새로 github에 가입한다. 나는 이 계정을 math-jh-bot으로 이름붙였다.

이제 봇 계정이 사이트 저장소에 pull request를 보낼 수 있도록 이를 collaborator에 추가해준다.

각종 암호키 설정

Staticman을 굴리기 위해서는 두 가지의 키, personal access token과 RSA 암호키가 각각 필요하다.

  • 아무리 봇 계정이라 하더라도 staticman에 이 계정의 접속 정보를 알려줄 수는 없다. 이럴 때를 위한 기능이 Github의 personal access token이다. Personal access token은 계정의 비밀번호와 거의 비슷하지만, 처음 토큰이 생성될 때 사용을 허가한 기능만 사용할 수 있다.
  • Staticman을 통해 주고받는 정보에는 민감한 정보가 포함될 수 있다. 이를 암호화하기 위해 RSA 암호키가 필요하다.

이제 이 두 키를 각각 만들자. 우선 봇 계정으로 접속하여, Settings / Developer settings / Personal access tokens으로 접속한다.

Personal_access_tokens

이후 오른쪽 위에 보이는 Generate new token 버튼을 눌러 새로운 토큰을 생성한다. 그럼 다음과 같은 창이 나온다.

Generating_token

  • Note 항목은 아무렇게나 이름을 적어두면 된다.
  • Expiration의 경우, 해당 기간이 지나면 token이 만료되어 재발급을 해 줘야 한다. 나는 귀찮음을 피하기 위해 No expiration을 선택하였다.
  • Scope의 경우, repo 이하의 모든 항목, user 이하의 모든 항목을 체크해주면 충분하다.

이후 토큰을 생성하면 다음과 같은 화면이 나오는데, 초록색 박스 안에 있는 키를 잘 복사해서 어디에 넣어두자. 위의 파란 경고문에서 알려주듯, 이 화면을 나가면 이 키를 확인할 방법이 없으므로 잃어버린다면 토큰을 새로 만들어야 한다.

Token

방금 만든 테스트용 키는 보이지만, 이전에 만든 math-jh-staticman의 키는 보이지 않는다.

나는 이를 내 사이트의 로컬 저장소에 Token이라는 이름으로 저장해두고, .gitignoreToken을 추가하여 원격 저장소로 업로드되지는 않도록 해 두었다.

RSA 암호키의 경우, 터미널에서

ssh-keygen -m PEM -t rsa -b 4096 -C "staticman key" -f ~/.ssh/staticman_key

을 입력하면 암호키 staticman_key와 이에 해당하는 공개키가 사용자 폴더의 숨은 폴더 .ssh에 생성된다.

Heroku 가입 및 staticman 설치

댓글 기능이 정상적으로 작동하기 위해서는 staticman이 항상 돌아가고 있는 상태여야 한다. 집에 가정용 서버가 있거나 하지 않는 한은 이것이 불가능한 일이므로, 우리는 Heroku를 이용한다. 언제나처럼 회원가입을 마친 후 Heroku에서 staticman을 검색하여 들어간다. 혹은 링크를 누르면 Heroku 상의 staticman 페이지로 이동한다.

Heroku

우측 상단에 Deploy to Heroku 버튼을 누르면 이 앱을 내 Heroku 계정에 설치할 준비가 완료된다. 적당히 앱 이름을 지어준 후, deploy app을 누르고 잠시 기다리면 Heroku 상에서 어플리케이션이 설치된다.

이제 앞서 만들었던 두 개의 키 (Github 토큰과 RSA키)를 Heroku에 설치된 staticman에 입력해주어야 한다. 여기에는 두 가지 방법이 있는데, 나는 둘째 방법으로 하다가 실수를 했는지 실패했고, 첫 번째 CLI를 이용하는 방법으로 성공했다. 이 방법은 실수할 가능성이 거의 없으므로 이 방법부터 소개한다.

Heroku CLI를 이용하는 방법

다음 커맨드를 터미널에 입력해서 Heroku CLI를 설치하자.

brew tap heroku/brew && brew install heroku

설치가 완료된 후, 다음 커맨드

heroku login

을 입력해서 로그인을 진행한다. 이제

  • 앞서 지정한 Heroku 상에서의 staticman의 이름을 [[Heroku 앱 이름]]
  • 봇 계정의 Github personal access 토큰을 [[Github 토큰]]

라고 하자. 다음 명령어

heroku config:add --app [[Heroku app 이름]] "RSA_PRIVATE_KEY=$(cat ~/.ssh/staticman_key | tr -d '\n')"
heroku config:add --app [[Heroku app 이름]] "GITHUB_TOKEN=[[Github 토큰]]"
코드 하이라이트가 다소 이상하게 되었지만 무시하자.

를 한 줄씩 입력하면 Heroku에 설치된 staticman에 RSA_PRIVATE_KEY와 GITHUB_TOKEN이 입력된다.

Heroku 홈페이지에서 입력하는 방법

혹은 Heroku에 설치된 어플리케이션의 settings에 들어가면 둘째 항목 Config Vars에 앞서 말한 두 개의 키를 직접 입력할 수 있다. Reveal Config Vars를 눌러보면 아직 어떠한 config variable도 없으므로 비어있다.

파인더에서 ⌘+⇧+G를 눌러 ~/.ssh로 이동하자. 그럼 아까 생성한 staticman_key가 보인다. 이를 열어보면

-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----

와 같은 형식의 키가 나온다. 이제 모든 줄 사이의 줄바꿈을 없애서 이를 한 줄로 만든다. 따라서 대략 키는

---BEGIN RSA PRIVATE KEY-----niwqafno3nqdaws ... -----END RSA PRIVATE KEY-----

처럼 생겨야 한다. 이제 이를 복사하여

KEY: RSA_PRIVATE_KEY, VALUE: 붙여넣기

이렇게 첫 번째 키를 입력한다. 둘째 키는

KEY: GITHUB_TOKEN, VALUE: 앞서 얻은 Github personal access token

을 입력하면 된다.

둘 중 어떠한 방법을 선택하든, 이제 https://[[Heroku 앱 이름]].herokuapp.com에 접속하면

Hello from Staticman version 3.0.0!

한 줄로 이루어진 웹페이지가 나와야 정상적으로 세팅이 완료된 것이다.

reCaptcha 가입

Staticman은 utterances 등과는 다르게 어떠한 계정도 없이 댓글을 등록할 수 있으므로, 상대적으로 스팸에 취약하다. 때문에 reCaptcha 등으로 보호하는 것이 필요하다.

https://www.google.com/recaptcha/admin/create으로 이동하여 구글 계정으로 로그인하면 reCaptcha에 가입할 수 있다. reCaptcha 유형의 경우, 나는 reCaptcha v2, “로봇이 아닙니다.” 체크박스를 이용하고 있다. 도메인은 블로그의 주소를 입력하고 reCaptcha를 생성하면 사이트 키와 비밀 키가 나온다. 이 두 키도 헷갈리지 않게 잘 적어두자.

저장소에서 staticman 사용 설정하기

이제 저장소 바깥에서 해야 할 준비는 모두 끝났으므로, 저장소의 파일들을 수정해주기만 하면 된다.

_config.yml 설정

우선 _config.yml에서 comments: 아래의 provider"staticman_v2"로 바꾸어 staticman_v2를 댓글 작성에 사용할 것임을 선언한다. 또 staticman: 아래 항목을

staticman:
    branch      : "main" 혹은 "master"
    endpoint    : "https://[[Heroku  이름]].herokuapp.com/v2/entry/"

로 입력해준다. 이 때 branch 태그는 본인 사이트 저장소의 메인 브랜치가 main이면 main으로, master면 master로 입력하면 된다.

바로 밑의 reCaptha의 siteKey는 reCaptcha에서 나온 그대로 입력하면 된다. 다만 공개된 파일인 _config.yml에 비밀키를 그대로 입력할 경우 github 저장소를 통해 모든 사람이 비밀키를 알 수 있게 된다. 따라서 아까 만든 RSA 키를 가지고 비밀키를 암호화한다. 다음 url

https://[[Heroku 앱 이름]].herokuapp.com/v2/encrypt/[[reCaptcha 비밀키]]

으로 이동하면 RSA 키를 이용해 암호화된 reCaptcha 비밀키가 얻어진다. 이를 reCaptcha 밑의 secret에 붙여넣는다. 예컨대 나는

reCaptcha:
  siteKey                : "6LeiUpkgAAAAAN3VR6VD-g9b10-yf2r8DjRmfiVZ"
  secret                 : "AE8k5EQKTRnYyp/vpHoMUhAH/YVkzv36tfI+ZZZ2N5c/pFI7Afio0TQfD/FJdDUOpJ8UH+n5K1x7Yeqc5tbcfG20rpKpWCXTiehLJSqiMixuj12oPGzg7iD4Qecehc9o02vSk6pRS/lCAoxuO2GU9zTs8EyKYSqgCkLIKEzoZDnnVJKvpWoPhhpqmaogaYG0AvuLyoKwDoN1C8EwZlGg69btCtVgtcIWgCscVb4eOlc/TH+b3FbKAn2XfLlRmRaDhGpsAl9HMWoIFURnqQ4ZDkcD3S6H9tGNrJd1uUtsWuhnXz7iz5nvg2Z82aWqOD8hqAaCH/jLpKWFNJiFVGqjgzVmxn665k9NCGmPx7rFPUkmQN8MFjJi1WO+w1WmUTdf0N6+A253f9Ls5ZKqJHRsoXDubd9c9bTuFI1YmaTiXiU45vJXMQRTgqO2XgWSbIANigWC4fQXa6D42pFZMVlOs9zjQ2dww84hMfNjqA1djAGf7C4oWBMNZqzyDiPbWvwBFZ1MQqSBq1SWrrrxF1l501U9GrYaOpgFlKBm0d1l4BE5Wft176EOzkyGGvgzhKfWMDla8CrOAQD8Qglpz3chIMAVTXucCIjvK74yNuNloZsXcGqGaFQguNsa9EhiAhWwviHFQmVCcxOl2bbMuc1QN0qsQC1TO1J0cQs9kbfWd8c="

댓글남기기