リアルタイム共同編集ツール『HackMD』をオンプレ運用して、好きな機能を追加して幸せになった話

## はじめに

最近、ふと自分の寝顔が気になるので録画して観てみたのですが、
顎が完全に無くなっており完全にピピ美ちゃん状態になっていた @k-waragai です。
痩せようと決意した12月でした。

さて、みなさんは仕事中に作業ログなどを残していますか？

自分は言語化することで見落としが無いことを確認したり他の人へ共有する目的としてメモをしています。

実装する中で分かった事実や背景、バグなどをメモとしてまとめながら進めることで
PR 作成時の Description を作る際にも役に立ち時短にも繋がっています。 
~~ﾍﾟｯって貼り付けるだけで済んだりするので楽~~

メモツールとして有名なものを上げると

- [Evernote](https://evernote.com/intl/jp/)
- [HackMD](https://hackmd.io/)
- Qiita もしくは Qiita Team
- [DocBase](https://docbase.io/)

あたりを使っている人が多いのではないでしょうか？

その中でも今回は 『 **HackMD** 』 に目を付けて
オンプレ運用してみたら好きな機能を追加出来て幸せになった話を書いていこうかなと思います。
(**全面的に「HackMD」最高だからみんな使おうぜっていう記事です**)

## なぜHackMDがいいの？比較してみた

メモツールとして有名なものを比較してみると DocBase と HackMD がいい感じということが分かりました。
※ あくまで個人的な感想を元にまとめています

| サービス名 | 共同編集 | 画像upload | マークダウン | メモ単位のロール制限機能 | 料金   | 個人的な好み
| :---:    | :---:  | :---:     | :---:     | :---: | :--- | :---: |
| Evernote | ×      | △        | △       | - | ベーシック: 無料<br>プラス: 年額3,100円 | × |
| HackMD   | ◯     | ◯         | ◯      | ◯ | 個人利用: 無料<br>エンタープライズ: 要相談<br>(人数にもよりますが1人500円程)| Love |
| Qiita Team| ×    | ◯        | ◯       | △ | Micro: 月額1,520円<br>Extra: 月額15,300円 | △ |
| DocBase  | ◯     | ◯         | ◯      | ◯ | スターター: 月額900円<br>プレミアム: 月額19,500円 | △ |

個人的に ドキュメントとして利用するには、Qiita Team もしくは DocBase がいいとは思うのですが作業メモ程度で使う分にはオーバースペックでした。
重要なドキュメントと混じってしまうのであまり良くありませんでした。

DocBaseであれば Private というロール設定があるので自分のメモ板用に1つドキュメントを作ってという使い方をしていました。
ただ、めんどくさがりの自分は保存ボタンを押すのですら **面倒くさい** ので長くは続きませんでした。
(最終的に Atom でメモ取ってました)

そこでいろいろ試した中で良かったのが『 **HackMD** 』でした。

HackMDはリアルタイム共同編集が可能で保存ボタンとかは特になく **常に保存** されます。
また PDF や Slide などへもエクスポートできます。
自分だけが見れるPrivateや他者の編集を不可能にするLockedなど5種類の設定が行なえます。

## HackMD と CodiMD についての概要説明

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177207/76755e47-963c-08f7-d676-c7deacea1f1b.png)

HackMD は **リアルタイムマークダウン共同編集ツール** として **無料** で提供されております。

また HackMD にはオープンソースバージョンがあり  **CodiMD**  というものがあります。

👉 [hackmdio/codimd: CodiMD - Realtime collaborative markdown notes on all platforms.](https://github.com/hackmdio/codimd)

 **CodiMD**  では HackMD の大部分の機能を **オープンソースとして提供** しており
これらをクローンするだけで簡単に手元でHackMDを動かす事が可能です。

ライセンスも AGPL となっています。

 **素晴らしいな、おい...。。。** 

しかも、ここ最近でドキュメントの整備などもしっかりと行われており充実してきています。
👉 [CodiMD Documentation - HackMD](https://hackmd.io/c/codimd-documentation/%2Fs%2Fcodimd-documentation)

さらにさらに！
 **Heroku**  へのワンクリック構築ボタンがあったり、
👉 https://hackmd.io/c/codimd-documentation/%2Fs%2Fcodimd-heroku-deployment

 **k8s helm**  が用意されていたり、
👉 https://hackmd.io/c/codimd-documentation/%2Fs%2Fcodimd-kubernetes-deployment

 **Docker Image**  が用意されていたり、
👉 https://hackmd.io/c/codimd-documentation/%2Fs%2Fcodimd-docker-deployment

多方面へのカバーも充実しているので CodiMD 様様って感じです٩(๑`ω´๑)۶

## まずは clone してみよう

node は `Node.js 8.x LTS up to 11.x.` となっているようです。
(なので 8以上12以下にしてねって事らしい)

余談ですが、自分が1年ほど前にやった時は node 6系 以上は保証しないよ！と言ってたはずなのに
気づいたらバージョン対応しているので結構メンテされてますね(ㆁωㆁ*) 控えめに言って最高か
ローカル開発用にDockerとかも自分で構築した良い思い出...

```shell:MacOS

$ git clone https://github.com/hackmdio/codimd.git
$ cd codimd

```

## 環境変数や設定ファイルを眺めて必要な情報をまとめてみた

環境変数についてはこちらを見ると良いです。

- [Configuration](https://github.com/crowdworks/codimd#configuration)

本番運用で最低限設定すべき項目は以下の通り

### config.json

| environment | example value | description |
|:---:|:---:|:---|
| loglevel | info | 標準出力に提供されるログの種類を定義します。 |
| domain | gyokuro-team.com | ドメイン名 |
| useSSL | true | SSLを利用するか |
| protocolUseSSL | true | SSLプロコトルを利用するか |
| urlAddPort | 80 | アプリケーションのポート番号 |
| allowAnonymousEdits | false | 匿名ユーザーの編集を可能にするか |
| defaultPermission | private | デフォルトのPermissionの設定<br>- freely: 誰でも閲覧編集可能<br>- editable: サインインしていれば閲覧編集可能<br>- limited: ゲスト以外は閲覧編集可能<br>- locked: オーナのみ編集可能<br>- protected: ゲスト以外のオーナのみ<br>- private: オーナのみしか閲覧編集はできない |
| allowEmailRegister | false | Eメールでの登録を可能にするか |
| email | false | Eメールでのサインインを許可するか |
| allowGravatar | true | アバターを利用するか |
| sslKeyPath | ./cert/privkey.pem | SSLを使用する際にprivkeyの場所を指定 |
| sslCertPath | ./cert/cert.pem | SSLを使用する際にcertの場所を指定 |
| dhParamPath | ./cert/fullchain.pem | SSLを使用する際にFullChainの場所を指定 |
| sslCAPath | ./certchain.pem/ | SSLを使用する際にCAの場所を指定 |
| imageuploadtype | s3 | アップロードの際の画像の置き場<br>- imgur<br>- s3<br>- minio<br>- azure<br>- filesystem |
| github | {clientID: "x", clientSecret: "x"} | githubでの登録を可能にする場合 |
| s3bucket | hackmd-bucket | s3でのアップロードを指定した場合 |
| db | いろいろ | dbの設定情報 |

### 環境変数

| environment | example value | description |
|:---:|:---:|:---|
| NODE_ENV | production | 環境状況 |
| HMD_IMAGE_UPLOAD_TYPE | s3 | 画像のアップロード方法 |
| CMD_DOMAIN | production | ドメイン名 |
| CMD_URL_ADDPORT | production | ポート番号 |
| HMD_DB_URL | 例: mysql://hackmd:hackmdpass@127.0.0.1:3306/hackmd | mysqlやpostgresqlのurlを置く |

## ローカルで動かしてみよう(Docker)

ドキュメントにもありますが codimd 用の image が配布されています。
`image: nabo.codimd.dev/hackmdio/hackmd:1.4.0`
👉 [Docker Deployment](https://hackmd.io/c/codimd-documentation/%2Fs%2Fcodimd-docker-deployment)

ただし今回はそれを使いません。
理由としては、自分が過去にやっていた時は node 6.x系 になっており
当時 11.x系 が最新であったことから、どうしても11.x系にしたく **必要最小限構成で自作** したのでそれで紹介します。

### Step1. docker-compose.yml の作成

```shell:MacOS

$ touch docker-compose.yml

```

```yaml:docker-compose.yml

version: '3'
services:

  # MySQL
  database:
    image: mariadb:10.4.4
    container_name: hackmd_mysql
    environment:
      MYSQL_USER: "${MYSQL_USER:-hackmd}"
      MYSQL_PASSWORD: "${MYSQL_PASSWORD:-hackmdpass}"
      MYSQL_DATABASE: "${MYSQL_DATABASE:-hackmd}"
      MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:-rootpassword}"
    volumes:
      - database:/var/lib/mysql
      - ./provisioning/resources/utf8mb4.cnf:/etc/mysql/conf.d/utf8mb4.cnf
    restart: always
    ports:
      - "${MYSQL_USE_PORT:-3306}:3306"
    networks:
      - app-net

  # Node.js
  app:
    build:
      context: .
      dockerfile: ./provisioning/Dockerfile
    container_name: hackmd_app
    tty: true
    volumes:
      - .:/app
      - /app/node_modules
    working_dir: /app
    env_file:
      - ./provisioning/docker-env
    restart: always
    depends_on:
      - database
    ports:
      - "${APP_USE_PORT:-3000}:3000"
    networks:
      - app-net

networks:
  app-net:
    driver: bridge

volumes:
  database:

```

### Step2. Dcokerfile などを作成

```shell:MacOS

$ mkdir -p provisioning/resources
$ touch provisioning/Dockerfile
$ touch provisioning/resources/utf8mb4.cnf
$ touch provisioning/docker-env
$ touch provisioning/resources/docker-entrypoint.sh

```

- Dockerfile

```text:provisioning/Dockerfile

FROM node:11.14.0-stretch

ENV DEBCONF_NOWARNINGS=yes

# Set some default config variables
ENV DOCKERIZE_VERSION=v0.6.1

RUN wget https://github.com/jwilder/dockerize/releases/download/${DOCKERIZE_VERSION}/dockerize-linux-amd64-${DOCKERIZE_VERSION}.tar.gz && \
    tar -C /usr/local/bin -xzvf dockerize-linux-amd64-${DOCKERIZE_VERSION}.tar.gz && \
    rm dockerize-linux-amd64-${DOCKERIZE_VERSION}.tar.gz

RUN mkdir /app
WORKDIR /app
COPY . /app

# update
RUN apt-get update

# install
RUN apt-get install -y build-essential vim

RUN yarn install && \
    yarn global add webpack && \
    npm run build

EXPOSE 3000

COPY ./provisioning/resources/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]

CMD ["node", "app.js"]

```

- utf8mb4.cnf

```text:provisioning/resources/utf8mb4.cnf

# MariaDB-specific config file.
# Read by /etc/mysql/my.cnf

[client]
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4

[mysqld]
collation-server=utf8mb4_general_ci
init-connect='SET NAMES utf8mb4'
character-set-server=utf8mb4

# Import all .cnf files from configuration directory
!includedir /etc/mysql/mariadb.conf.d/

```

- docker-env

```text:.envrc

# dockerで動かすのはdevelopment前提です。

NODE_ENV=development
CMD_CONFIG_FILE=config.json
DEBUG=true
CMD_LOGLEVEL=info
CMD_PROTOCOL_USESSL=false
CMD_URL_ADDPORT=false
CMD_USECDN=true
CMD_ALLOW_ANONYMOUS=false
CMD_ALLOW_ANONYMOUS_EDITS=false
CMD_ALLOW_FREEURL=false
CMD_DEFAULT_PERMISSION=limited
CMD_EMAIL=false
CMD_ALLOW_PDF_EXPORT=true
CMD_ALLOW_EMAIL_REGISTER=false
CMD_ALLOW_GRAVATAR=true
CMD_IMAGE_UPLOAD_TYPE=filesystem

# Change Require
CMD_DB_URL=mysql://hackmd:hackmdpass@database:3306/hackmd
# Githubログイン使いたいなら
# CMD_GITHUB_CLIENTID={YOUR_GITHUB_CLIENT_ID}
# CMD_GITHUB_CLIENTSECRET={YOUR_GITHUB_SECRET_KEY}

# option: Docker Environment
# MYSQL_USER=
# MYSQL_PASSWORD=
# MYSQL_DATABASE=
# MYSQL_ROOT_PASSWORD=
# APP_USE_PORT=

```

- docker-entrypoint.sh

```shell:provisioning/resources/docker-entrypoint.sh

#!/usr/bin/env bash

dockerize -wait tcp://database:3306 -timeout 30s

node_modules/.bin/sequelize db:migrate

exec "$@"

```

### Step3. configファイル等を整える



```shell:MacOS

$ cp config.json.example config.json
$ cp .sequelizerc.example .sequelizerc

```


- config.json

一旦、SAML とか 画像S3に上げるととかは一旦考えず最小限で行う。

ログインは Githubログイン で行いたいが後で変更するためここでは行わない。

```json:config.json

{
  "development": {
    "useSSL": false,
    "tmpPath": "./tmp/",
    "defaultNotePath": "./public/default.md",
    "docsPath": "./public/docs",
    "viewPath": "./public/views",
    "uploadsPath": "./public/uploads"
  }
}

```

### Step4. docker up してみよう

```shell:MacOS

$ docker-compose up -d

```

これで `localhost:3000` にアクセスすれば見れるので
ローカルでの開発が捗る٩(๑`ω´๑)۶

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177207/5e8eaba5-b23a-2345-5c9b-301c22054ead.png)


 **※ もし アクセス後 ビルドエラーのような画面が出た場合は以下をお試しください。** 

```
$ docker-compose run --rm app npm run build
$ docker-compose run --rm yarn install
```

### Step5. タイトルを変えてみよう

`public/views/index/body.ejs` の 43行目付近にある以下を修正

```diff

- <h1 class="cover-heading"><i class="fa fa-file-text"></i> CodiMD</h1>
+ <h1 class="cover-heading"><i class="fa fa-file-text"></i> k-waragaiMD</h1>

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177207/6cf20739-873c-3b90-d26a-fa096ea5dc7a.png)


これでやる気UP (´∀｀∩)↑age↑

### Step6. Githubログインを出来るようにしてみよう

👉 https://github.com/settings/developers にアクセスを行い `New OAuth App` をクリック

必要な情報を入力して `Register application` をクリック

<img width="640" alt="OAuth_application_settings.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177207/83db472d-934e-9347-77c9-590b66d7f299.png">


`ClientID` と `SecretKey` が吐き出されるのでそれをメモします。

<img width="640" alt="OAuth_application_settings.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177207/984fcc75-b355-4df7-6e86-0570d044c252.png">


一度 `docker-compose down` を行います。

docker-env を編集してください。

```diff:docker-env

...

  # Change Require
  CMD_DB_URL=mysql://hackmd:hackmdpass@database:3306/hackmd
  # Githubログイン使いたいなら
- # CMD_GITHUB_CLIENTID={YOUR_GITHUB_CLIENT_ID}
- # CMD_GITHUB_CLIENTSECRET={YOUR_GITHUB_SECRET_KEY}
+ CMD_GITHUB_CLIENTID=SampeClientIdDesu
+ CMD_GITHUB_CLIENTSECRET=SampeSecretKeyDesu

  # option: Docker Environment
  # MYSQL_USER=
  # MYSQL_PASSWORD=
  # MYSQL_DATABASE=
  # MYSQL_ROOT_PASSWORD=
  # APP_USE_PORT=

```

編集が完了したら `docker-compose up -d` をすると
先程は無かった `サインイン` のボタンが現れるようになります。

試しにログインをすると、Githubからユーザー名などを取れておりログインできてる事がわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177207/a51beaed-a06f-54a1-ca01-abbca0e15da3.png)

## EC2で動かしてみよう(ホスト)

Ubuntu でざっくりと docker を使用せずに構築したいと思います。
基本的なセキュリティの設定などは各々あると思いますので省かせていただきます。

すでにEC2にsshしている状態でのお話になります。

### Step1. 事前準備

- apt update upgrade

```shell:EC2インスタンス[ubuntuユーザー]

$ sudo apt update
$ sudo apt upgrade

```

### Step2. ユーザーの作成とsudoグループ追加


```shell:EC2インスタンス[ubuntuユーザー]
# ユーザーの作成
$ sudo adduser hackmd
    Adding user `hackmd' ...
    Adding new group `hackmd' (1001) ...
    Adding new user `hackmd' (1001) with group `hackmd' ...
    Creating home directory `/home/hackmd' ...
    Copying files from `/etc/skel' ...
    Enter new UNIX password: passwordを入力...
    Retype new UNIX password: passwordを入力...
    passwd: password updated successfully
    Changing the user information for hackmd
    Enter the new value, or press ENTER for the default
        Full Name []:
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:

# sudo グループへ hackmd ユーザーを追加
$ sudo gpasswd -a hackmd sudo

# スイッチッ!!!!!!
$ sudo su - hackmd
```

### Step3. 必要なパッケージのインストール

- コアになりそうなもののインストール

```shell:EC2インスタンス[hackmdユーザー]

[hackmd]$ sudo apt-get install -y git build-essential wget vim unzip libssl-dev python

```

- nvm のインストール

```shell:EC2インスタンス[hackmdユーザー]

[hackmd]$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.35.0/install.sh | bash
[hackmd]$ export NVM_DIR="$HOME/.nvm"
[hackmd]$ [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[hackmd]$ [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
[hackmd]$ source ~/.bashrc
[hackmd]$ nvm --version
0.35.0
```

- node のインストール

```shell:EC2インスタンス[hackmdユーザー]

[hackmd]$ nvm install v11.14.0
[hackmd]$ nvm use v11.14.0
[hackmd]$ node -v
11.14.0
```

- yarn のインストール

```shell:EC2インスタンス[hackmdユーザー]

[hackmd]$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
[hackmd]$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
[hackmd]$ sudo apt-get update && sudo apt-get -y install yarn
[hackmd]$ export PATH="$PATH:/opt/yarn-1.15.2/bin"
[hackmd]$ source ~/.bashrc
[hackmd]$ yarn -v
1.15.2
```

- mariaDBインストール(MySQLでも良い)

```shell:EC2インスタンス[hackmdユーザー]

[hackmd]$ curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash
[hackmd]$ sudo apt -y update
[hackmd]$ apt install -y mariadb-server-10.3
[hackmd]$ mysql_secure_installation


        Enter current password for root (enter for none):
        > パスワード

        Change the root password? [Y/n]
        > Y
        > New password: パスワード
        > Re-enter new password: パスワード

        Remove anonymous users? [Y/n]
        > n

        Disallow root login remotely? [Y/n]
        > Y

        Remove test database and access to it? [Y/n]
        > Y

        Reload privilege tables now? [Y/n]
        > Y

[hackmd]$ mysql -uroot -p -h 127.0.0.1 -e "show databases;"
> ルートパスワード

        +--------------------+
        | Database           |
        +--------------------+
        | information_schema |
        | mysql              |
        | performance_schema |
        +--------------------+

# ユーザー作成
[hackmd]$ mysql -uroot -p -h 127.0.0.1 -e "CREATE USER ユーザー名 IDENTIFIED BY 'パスワード';"

# 権限付与
[hackmd]$ mysql -uroot -p -h 127.0.0.1 -e "GRANT ALL ON *.* TO 'ユーザー名'@'localhost' IDENTIFIED BY 'パスワード';"

# 適用
[hackmd]$ mysql -uroot -p -h 127.0.0.1 -e "FLUSH PRIVILEGES;"

[hackmd]$ mysql -uユーザー名 -p -h 127.0.0.1 -e "create database データベース名;"
```

- nginx

```shell:EC2インスタンス[hackmdユーザー]

[hackmd]$ curl http://nginx.org/keys/nginx_signing.key | sudo apt-key add -
[hackmd]$ VCNAME=`cat /etc/lsb-release | grep DISTRIB_CODENAME | cut -d= -f2` && sudo -E sh -c "echo \"deb http://nginx.org/packages/ubuntu/ $VCNAME nginx\" >> /etc/apt/sources.list"
[hackmd]$ VCNAME=`cat /etc/lsb-release | grep DISTRIB_CODENAME | cut -d= -f2` && sudo -E sh -c "echo \"deb-src http://nginx.org/packages/ubuntu/ $VCNAME nginx\" >> /etc/apt/sources.list"
[hackmd]$ sudo apt-get update
[hackmd]$ sudo apt-get install -y nginx

```

### Step4. リポジトリのクローンと設定

github の `.netrc` などの設定は一旦省かせていただきます。
また config.json や sequelizerc はローカルのときとほとんど同じとなっていますのでSKIPしていただいても構いません。
ただ、SSLの関係上環境変数などが若干増えています。

```shell:EC2インスタンス[hackmdユーザー]

[hackmd]$ git clone https://github.com/hackmdio/codimd.git
[hackmd]$ cd codimd
[hackmd]$ cp .sequelizerc.example .sequelizerc
[hackmd]$ cp config.json.example config.json

```

- utf8mb4.cnf の設定

```shell:EC2インスタンス[hackmdユーザー]

[hackmd]$ sudo vim /etc/mysql/mariadb.conf.d/utf8mb4.cnf

```

```text:/etc/mysql/mariadb.conf.d/utf8mb4.cnf

    # MariaDB-specific config file.
    # Read by /etc/mysql/my.cnf

    [client]
    default-character-set=utf8mb4

    [mysql]
    default-character-set=utf8mb4

    [mysqld]
    collation-server=utf8mb4_general_ci
    init-connect='SET NAMES utf8mb4'
    character-set-server=utf8mb4

    # Import all .cnf files from configuration directory
    !includedir /etc/mysql/mariadb.conf.d/

```

- config.json の設定

- 画像は S3 へ アップロード
- ログインは メール登録を禁止しGithubログインのみ
- ゲストユーザーは書き込み閲覧を出来ない
- ドメインの指定

という形でconfigを作っていきます。

SSLはこの段階では行わないが後述で行います。

Github の 

```shell:EC2インスタンス[hackmdユーザー]

[hackmd]$ vim config.json

```

```json:config.json

{
  "production": {
    "loglevel": "info",
    "useSSL": false,
    "protocolUseSSL": false,
    "domain": "xxxxxxxxxxx.com", #--ドメインがあるのであれば
    "urlAddPort": "80",
    "allowAnonymousEdits": false,
    "defaultPermission": "private",
    "allowEmailRegister": false,
    "email": false,
    "allowGravatar": true,
    "db": {
      "username": "xxxxxxxxxxx",
      "password": "xxxxxxxxxxx",
      "database": "xxxxxxxxxxx",
      "host": "127.0.0.1",
      "port": "3306",
      "dialect": "mysql"
    },
    "github": {
      "clientID": "xxxxxxxxxxxxxxxxxxxxxx",
      "clientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    },
    "imageuploadtype": "s3",
    "s3": {
      "accessKeyId": "xxxxxxxxxxxxxxxxxxxxxx",
      "secretAccessKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "region": "ap-northeast-1"
    },
    "s3bucket": "codimd-production"
  }
}

```

- nginx の設定

```shell:EC2インスタンス[hackmdユーザー]

[hackmd]$ sudo vim /etc/nginx/conf.d/node-app.conf

```

```text:/etc/nginx/conf.d/node-app.conf

server {
  listen 80;
  # listen  443 ssl;
  ssl on;

  # 編集すること
  server_name  {YOUR_DOMAIN}.com;

  # 今はまだ使わない
  # ssl_certificate      /etc/letsencrypt/live/{YOUR_DOMAIN}.com/fullchain.pem;
  # ssl_certificate_key  /etc/letsencrypt/live/{YOUR_DOMAIN}.com/privkey.pem;

  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

  proxy_redirect                          off;
  proxy_set_header Host                   $host;
  proxy_set_header X-Real-IP              $remote_addr;
  proxy_set_header X-Forwarded-Host       $host;
  proxy_set_header X-Forwarded-Server     $host;
  proxy_set_header X-Forwarded-For        $proxy_add_x_forwarded_for;

  location / {
    proxy_pass https://localhost:3000;
      proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
  }

  # あとで SSL取得に使う
  location ^~ /.well-known/acme-challenge/ {
    default_type "text/plain";
    root         /home/hackmd/codimd/public;
  }

  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /home/hackmd/codimd/public;
  }
}

```

```shell:EC2インスタンス[hackmdユーザー]

[hackmd]$ sudo systemctl restart nginx

```

- 無料枠だとRAMに限界あるので SWAP領域を作成

```shell:EC2インスタンス[hackmdユーザー]

[hackmd]$  sudo dd if=/dev/zero of=/swap bs=1M count=1024
[hackmd]$  sudo mkswap /swap
[hackmd]$  sudo swapon /swap
[hackmd]$  sudo chmod 600 /swap

```

### Step5. 立ち上げ

```shell:EC2インスタンス[hackmdユーザー]

[hackmd:~/codimd]$ yarn install
[hackmd:~/codimd]$ yarn global add webpack
[hackmd:~/codimd]$ npm run build
[hackmd:~/codimd]$ node_modules/.bin/sequelize db:migrate
[hackmd:~/codimd]$ node app.js

```

### Step6. 永続的にappを立ち上げさせる

```shell:EC2インスタンス[hackmdユーザー]

[hackmd:~/codimd]$ npm install -g forever
[hackmd:~/codimd]$ forever start app.js

```

### Step7. SSL化(HTTPS)

今回は `Let's Encrypt` を利用します

- certbot challenge

```shell:EC2インスタンス[hackmdユーザー]

[hackmd:~/codimd]$ mkdir -p .well-known/acme-challenge/
[hackmd:~/codimd]$ chmod 644 .well-known/acme-challenge/
[hackmd:~/codimd]$ cd ~/
[hackmd:~/]$ git clone https://github.com/certbot/certbot.git && cd certbot
[hackmd:~/]$ sudo ./certbot-auto certonly --webroot -w /home/hackmd/codimd/public -d {YOUR_DOMAIN}.com -m {YOUR_MAIL_ADDR} --debug

```

- application側の対応

```shell:EC2インスタンス[hackmdユーザー]

[hackmd:~/]$ cd codimd

```

- 環境変数の変更

```shell:EC2インスタンス[hackmdユーザー]

[hackmd:~/codimd]$ direnv edit .

```

```diff:.ervrc
- export CMD_PROTOCOL_USESSL=false
+ export CMD_PROTOCOL_USESSL=true

```

- config.json の変更

```shell:EC2インスタンス[hackmdユーザー]

[hackmd:~/codimd]$ vim config.json

```

```diff:config.json
{
  "production": {
    "loglevel": "info",
-   "useSSL": false,
-   "protocolUseSSL": false,
+   "useSSL": true,
+   "protocolUseSSL": true,
+   "sslKeyPath": "/etc/letsencrypt/live/xxxxxxxxxxx.com/privkey.pem",
+   "sslCertPath": "/etc/letsencrypt/live/xxxxxxxxxxx.com/cert.pem",
+   "dhParamPath": "/etc/letsencrypt/live/xxxxxxxxxxx.com/fullchain.pem",
+   "sslCAPath": ["/etc/letsencrypt/live/xxxxxxxxxxx.com/chain.pem"],
    "domain": "xxxxxxxxxxx.com", #--ドメインがあるのであれば
    "urlAddPort": "80",
    "allowAnonymousEdits": false,
    "defaultPermission": "private",
    "allowEmailRegister": false,
    "email": false,
    "allowGravatar": true,
    "db": {
      "username": "xxxxxxxxxxx",
      "password": "xxxxxxxxxxx",
      "database": "xxxxxxxxxxx",
      "host": "127.0.0.1",
      "port": "3306",
      "dialect": "mysql"
    },
    "github": {
      "clientID": "xxxxxxxxxxxxxxxxxxxxxx",
      "clientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    },
    "imageuploadtype": "s3",
    "s3": {
      "accessKeyId": "xxxxxxxxxxxxxxxxxxxxxx",
      "secretAccessKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "region": "ap-northeast-1"
    },
    "s3bucket": "codimd-production"
  }
}

```

- nginx の変更

```shell:EC2インスタンス[hackmdユーザー]

[hackmd:~/codimd]$ sudo vim /etc/nginx/conf.d/node-app.conf

```

```text:/etc/nginx/conf.d/node-app.conf

upstream node-app {
  server localhost:3000;
}

server {
  listen  443 ssl;
  ssl on;

  server_name  xxxxxxxxxxxx.com;

  ssl_certificate      /etc/letsencrypt/live/xxxxxxxxxxx.com/fullchain.pem;
  ssl_certificate_key  /etc/letsencrypt/live/xxxxxxxxxxx.com/privkey.pem;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

  proxy_redirect                          off;
  proxy_set_header Host                   $host;
  proxy_set_header X-Real-IP              $remote_addr;
  proxy_set_header X-Forwarded-Host       $host;
  proxy_set_header X-Forwarded-Server     $host;
  proxy_set_header X-Forwarded-For        $proxy_add_x_forwarded_for;

  location / {
    proxy_pass http://node-app/;
  }

  location ^~ /.well-known/acme-challenge/ {
    default_type "text/plain";
    root         /home/hackmd/codimd/public;
  }

  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /home/hackmd/codimd/public;
  }
}

```

```shell:EC2インスタンス[hackmdユーザー]

[hackmd:~/codimd]$ sudo systemctl restart nginx

```

### Step8. Restart

```shell:EC2インスタンス[hackmdユーザー]

[hackmd:~/codimd]$ forever app.js restart

```

アクセス！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177207/5b504471-8685-a6c6-461f-71e3073f6eba.png)

Github の `New OAuth App` はSSLが出来たタイミング等で先程と同じ手順で行ってください。

ちゃんと鍵も出てるし ログインも出来る〜。 \  最    高  /

### トラブルシューティング

1. SSL化にて challenge に失敗した場合、原因として `.well-known/acme-challenge/` にアクセス出来てない可能性があります。
    - `chmod 644 .well-known/acme-challenge/` などをしてみてください。
    - また、 challenge 時 80番(HTTP)でアクセスが来るはずなので 更新の際は `listen 80` にするのを忘れずに
2. 今回はドメインの設定を公開してないですが タイプA の設定をしています。(google で買ったドメインを利用しています。)


## チームで使っている板を自動でQiita Teamに投稿できるようにしてみた

私達のチームでは、1つの板を専有し毎日更新し続けています。
その板に書いているものは以下の通りとなっております。

- 共有事項
    - 有給取る日やフレックス出社日など
- 今日のゴール
    - 今日終わらせるべきタスクを明確にする
- 作業ログ
    - 作業を行う際、個人個人がそれぞれh2でタイトルを切ってログ等をまとめたりする

今までは翌日の朝にこれらを全コピしてQiita Teamに転記していたのですが、
まあちょっとの繰り返しさえも自動化したくなるのがエンジニア魂ですよね(ㆁωㆁ*) ~~めんどくさいだけ~~

やり方は簡単で Qiita API を使ったスクリプトを Cron で平日毎日 22:00 頃に回すだけです。

sampleコードを載せておきます。

```ruby:post_daily_report_by_cosmos_team.rb

require 'net/https'
require 'mysql2'
require "json"
require "date"

mysql_username = ENV['MYSQL_USERNAME'].freeze
mysql_password = ENV['MYSQL_PASSWORD'].freeze
mysql_database = ENV['MYSQL_DATABASE'].freeze
team_note_id   = ENV['TEAM_NOTE_ID'].freeze
qiita_api_key  = ENV['QIITA_API_KEY'].freeze
domain         = ENV['TEAM_DOMAIN'].freeze

client = Mysql2::Client.new(
  socket: '/var/run/mysqld/mysqld.sock',
  username: mysql_username,
  password: mysql_password,
  encoding: 'utf8',
  database: mysql_database
)

statement = client.prepare('SELECT content FROM Notes WHERE id = ?')
# 事前にタイトルで select しておき ID を控えています。
# example: 
#   mysql -uhackmd -p hackmd -e "select id from Notes where title = 'cosmos開発日報'"
results = statement.execute(team_note_id)

weekday = ["日", "月", "火", "水", "木", "金", "土"]
today = Date.today
title = "cosmos開発日報" + today.strftime("%Y/%m/%d (#{weekday[today.wday]})")
content = ""

results.each do |row|
  content = row["content"]
end

# 組み立て
qiita = "https://#{domain}.qiita.com/"
path = '/api/v2/items'
url = qiita + path

uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)

http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

req = Net::HTTP::Post.new(uri.request_uri)
req["Authorization"] = "Bearer #{qiita_api_key}"
req["Content-Type"] = "application/json"

post_data = {
  title: title,
  body: content,
  coediting: true,
  group_url_name: "cosmos",
  private: false,
  tags: [{name: "cosmos"},{name: "日報"}],
  tweet: false,
}.to_json

# 投稿
req.body = post_data
# 結果
res = http.request(req)
puts res.code, res.msg

```

```shell:crontab

# Edit this file to introduce tasks to be run by cron.
00 13 * * 1-5 bash -cl 'cd /home/hackmd/cron_scripts/ && ruby -Ku post_daily_report_by_cosmos_team.rb'

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177207/224970ae-62bd-26a7-ed4b-3c1030739128.png)

毎日こんな感じで投稿されるようになりました！ \ 自 動 化 バ  ン ザ イ /

こんな感じで好きな機能を追加したりをしてみたり実験場としても活躍しています！


## 最後に

今回は docker で構築する方法と ホスト上に直接構築する方法の2種類を紹介いたしました！
応用すれば ECS にすることも出来ますね(ㆁωㆁ*)

エンジニアとして、「あのサービスにこの機能があったら最高なんだけど...」って思うことって多々ありますよね。

もともと利用してたHackMDをそのまま改造出来るって最高だなって思いました。 THE 幸せ

~~HackMDはエンタープライズ版があるとお話していましたが、エンタープライズ版では高頻度で504になり **自動復旧を待つ** ということが高頻度で起きました。~~
~~連続で起きるときもあれば起きないときもあり、不安定な状態が続いた為オンプレ化を進めたのが実背景となっています。~~

実際やってみるとそこまで難しいことはなく、 **機能の拡張** なども容易に行えてしばらくは遊べそうです(ㆁωㆁ*)

料金も EC2 インスタンスの無料枠(t2.micro)を使っていますが、アクティブユーザーは最大15人とかでも落ちるといったことは今までありませんでした。

画像もS3にあげているので容量圧迫とかも起きず快適に利用できています！

文字だけなのでmysql自体の負荷も少ないことから、 **社内で50人くらいで使う分には無料枠で全然問題ない** かと思われます。

ぜひ皆様も CodiMD を利用してみてくださいね(ㆁωㆁ*) bye

👉 [hackmdio/codimd: CodiMD - Realtime collaborative markdown notes on all platforms.](https://github.com/hackmdio/codimd)

またもっと良いツールがあるよ！って方はぜひ教えて下さい！

Twitterもやってるのでぜひフォローください。
👉 [つなまよ（@mayoxtuna）さん / Twitter](https://twitter.com/mayoxtuna)
Apex Legends(PC版)とか一緒に趣味開発してくれるお友達募集中です٩(๑`ω´๑)۶

## 次回予告

明日は クリスマス・イブ ですね٩(๑`ω´๑)۶ 🎄  **:.。.:May your Christmas wishes come true:.。.:**  🎄

12月24日は クラウドワークスエンジニアである @cesare による 「複業を支える技術」です！
お楽しみに〜bye

## 2019/12/24 追記

この記事を公開したあと Twitter 経由で HackMD の CEO の方から直々に感謝のDMを頂きました。
そこで エンタープライズ版の 504 になる問題について教えていただきました。

現在では**高パフォーマンスで動く状態を維持**しており、それらの問題は起きないとのことでした (ㆁωㆁ*)

日々進化し続ける HackMD 最高ですね٩(๑`ω´๑)۶