カテゴリー: サーバ管理

  • Let’s encrypt でワイルドカード証明書を新規取得する

    毎回調べてしまうので、手順をメモします。

    前提

    • certbot-auto が使える状態
      • git clone https://github.com/certbot/certbot
    • ドメインの DNS レコードが変更できる状態

    手順

    適宜、kuratsuki.net を自ドメインに置き換えてください。

    $ sudo ./certbot-auto certonly \
        --manual -d '*.kuratsuki.net' \
        -m '[email protected]' --agree-tos \
        --preferred-challenges dns-01 \
        --server https://acme-v02.api.letsencrypt.org/directory
    
    ./certbot-auto has insecure permissions!
    To learn how to fix them, visit https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/
    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    Plugins selected: Authenticator manual, Installer None
    
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Would you be willing to share your email address with the Electronic Frontier
    Foundation, a founding partner of the Let's Encrypt project and the non-profit
    organization that develops Certbot? We'd like to send you email about our work
    encrypting the web, EFF news, campaigns, and ways to support digital freedom.
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    (Y)es/(N)o: Y
    Obtaining a new certificate
    Performing the following challenges:
    dns-01 challenge for kuratsuki.net
    
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NOTE: The IP of this machine will be publicly logged as having requested this
    certificate. If you're running certbot in manual mode on a machine that is not
    your server, please ensure you're okay with that.
    
    Are you OK with your IP being logged?
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    (Y)es/(N)o: Y
    
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Please deploy a DNS TXT record under the name
    _acme-challenge.kuratsuki.net with the following value:
    
    Ib4LVMChbn2wyQZgvfbFXOkIO5HuDi7OJ9Xxjhp9-C2
    
    Before continuing, verify the record is deployed.
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Press Enter to Continue
    

    ここで一旦 DNS の設定に移ります。

    自分の場合はお名前.comなので次のように辿って設定します。

    1. 「ドメイン設定」
    2. 「ネームサーバーの設定」
    3. 「DNS 関連機能の設定」
    4. ドメインを選択して「次へ」
    5. 「DNSレコード設定を利用する」
    6. 「TYPE」を「TXT」として「ホスト名」を「_acme-challenge」にします
    7. 「VALUE」に先程 certbot-auto が出したハッシュを入力して「確認画面へ進む」
    8. 「設定する」

    DNS レコードの設定が終ったら certbot-auto に戻って Enter キーを押します。

    ここで DNS レコードの更新が反映されていないと次のような表示が出ます。

    Waiting for verification...
    Challenge failed for domain kuratsuki.net
    dns-01 challenge for kuratsuki.net
    Cleaning up challenges
    Some challenges have failed.
    
    IMPORTANT NOTES:
     - The following errors were reported by the server:
    
       Domain: kuratsuki.net
       Type:   unauthorized
       Detail: Incorrect TXT record
       "KQ08Sk3s2KCzWwsGrzJMU4GJksYY-8aGoSPuY3k1f8J" found at
       _acme-challenge.kuratsuki.net
    
       To fix these errors, please make sure that your domain name was
       entered correctly and the DNS A/AAAA record(s) for that domain
       contain(s) the right IP address.
    

    ローカルで反映されたのを確認してからやってもこの表示が出たので、別窓でもう一つ terminal を開いておいて次を試してみます。

    $ nslookup -q=txt _acme-challenge.kuratsuki.net
    Server:         127.0.0.53
    Address:        127.0.0.53#53
    
    Non-authoritative answer:
    _acme-challenge.kuratsuki.net   text = "KQ08Sk3s2KCzWwsGrzJMU4GJksYY-8aGoSPuY3k1f8J"
    
    Authoritative answers can be found from:
    

    この結果に反映されるまでは certbot-auto を続けても失敗するだけです。

    問題なければ次のような表示になります。

    Waiting for verification...
    Cleaning up challenges
    
    IMPORTANT NOTES:
     - Congratulations! Your certificate and chain have been saved at:
       /etc/letsencrypt/live/kuratsuki.net/fullchain.pem
       Your key file has been saved at:
       /etc/letsencrypt/live/kuratsuki.net/privkey.pem
       Your cert will expire on 2019-09-21. To obtain a new or tweaked
       version of this certificate in the future, simply run certbot-auto
       again. To non-interactively renew *all* of your certificates, run
       "certbot-auto renew"
     - If you like Certbot, please consider supporting our work by:
    
       Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
       Donating to EFF:                    https://eff.org/donate-le
    

    これで証明書諸々が /etc/letsencrypt/live/ に格納されました。

  • docker-compose で Wekan を構築

    docker-compose で Wekan を構築

    docker-compose.yaml

    wekan:
      image: wekanteam/wekan
      links:
        - wekandb
      environment:
        - MONGO_URL=mongodb://wekandb/wekan
        - ROOT_URL=http://kuratsuki.net/    # URLは各自変更してください
      ports:
        - 3001:8080 # 公開ポート3001を各自変更してください
      restart: always
    
    wekandb:
      image: mongo
      volumes:
        - ./mongodb/data:/data
      restart: always
    

    参考

  • docker-compose でお手軽に Redmine を構築する

    docker-compose でお手軽に Redmine を構築する

    自プロジェクトで Redmine を使う必要があって、その時の作業を記録として残します。

    前提

    • Docker が使える環境
      • docker-compose が使えること
    • Redmine のデータはコンテナを再構築しても永続化するようにしたい
      • 1サーバに複数の Redmine を構築する予定があるので、それも考慮する

    docker-compose.yaml

    version: '2'
    services:
      redmine:
        image: redmine
        ports:
          - 3000:3000
        environment:
          REDMINE_DB_MYSQL: db
          REDMINE_DB_PASSWORD: redmine
        depends_on:
          - db
        restart: always
    
      db:
        image: mariadb
        environment:
          MYSQL_ROOT_PASSWORD: redmine
          MYSQL_DATABASE: redmine
        command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
        volumes:
          - "./mysql/data:/var/lib/mysql"
        restart: always
    

    いきなりですが今回使った docker-compose.yaml の中身です。

    少しだけ補足すると、MariaDB のデータを docker-compose.yaml があるディレクトリ下に保存するように volume を割り当てているので、docker-compose down して再度 up してもデータが残ります。

    ポートの変更

    上記の yaml では 3000 で待ち受けていますが、8080:300080:3000 のように自分の使うポートに変更してください。
    :3000 はコンテナ内部のポートなので変更してはいけません。

    起動

    初回は次のようにしてコンテナを生成しつつ起動します。

    $ sudo docker-compose up -d
    

    stop で止めたりした時は start で再開できます。

    $ sudo docker-compose start
    

    停止

    一時的にコンテナを止めます。

    $ sudo docker-compose stop
    

    削除

    stop & rm です。

    $ sudo docker-compose down
    

    コンテナを削除しても ./mysql/* 下にデータが残っているので適宜削除してください。

    参考

  • docker-compose で建てた CodiMD(HackMD) を 1.2.0 から 1.3.1 に更新する

    docker-compose で建てた CodiMD(HackMD) を 1.2.0 から 1.3.1 に更新する

    DB のバックアップをしてコンテナを削除する

    最初に構築したディレクトリ(docker-compose.yml があるところ)に移動して PostgreSQL のバックアップを取得しておきます。

    データはホストに残っていますが、念のため実行しておきます。

    $ cd docker-hackmd
    $ sudo docker-compose exec database pg_dump hackmd -U hackmd  > backup.sql
    $ sudo docker-compose down
    Stopping docker-hackmd_app_1      ... done
    Stopping docker-hackmd_database_1 ... done
    Removing docker-hackmd_app_1      ... done
    Removing docker-hackmd_database_1 ... done
    Removing network docker-hackmd_backend

    docker-compose.yml を編集する

    services – app – image と辿って "hackmdio/hackmd:1.2.0" となっている部分を "hackmdio/hackmd:1.3.1" に変更します。

    ### 略 ###
    services:
      ### 略 ###
      app:
        ### 略 ###
        image: hackmdio/hackmd:1.3.1
    ### 略 ###

    新バージョンの image を取得する

    この手順は飛ばしても次の手順で自動で拾ってきます。

    $ sudo docker-compose pull
    Pulling database ... done
    Pulling app      ... done

    新バージョンでコンテナを作成して立ち上げる

    $ sudo docker-compose up -d
    Creating network "docker-hackmd_backend" with the default driver
    Creating docker-hackmd_database_1 ... done
    Creating docker-hackmd_app_1      ... done

    以上で完了です。
    データもそのまま残っているはずですが、バックアップからリストアするなら次を実行します。

    $ cat backup.sql | sudo docker exec -i $(docker-compose ps -q database) psql -U hackmd
  • シェルスクリプト (bash) でファイル名が ASCII 文字から始まるファイルのみを抽出する

    シェルスクリプト (bash) でファイル名が ASCII 文字から始まるファイルのみを抽出する

    TL; DR

    ^[[:graph:][:space:][:cntrl:]]+$
    

    ファイル名が上記の正規表現を満し、かつ下の条件を満たす場合に ASCII 文字から始まるファイル名と判断できます。

    echo -n "${filename:0:1}" | wc -c` == "1"
    

    [toc]

    経緯

    bash のシェルスクリプトでファイル名のマッチングを行っているときに正規表現の制限に見事にはまった記録です。

    目的はディレクトリパスを除いたファイル名が ASCII 文字から始まるものだけを抽出することです。
    ここで注意すべきなのは、Linux ではファイル名に含めない文字は NULL 文字(0x00)と /(0x4F) のみであることです。
    意外に感じるかもしれませんが ASCII 制御文字もファイル名に含めます。

    今回は作業を記録しながら進めたので、順を追って書いてあります。
    結論だけ欲しい場合は最後だけ読んでください。

    ディレクトリの中身を取得する

    #!/bin/bash
    
    for path in "$1"/*; do
        echo $path
    done
    

    これを “lsre.sh” と保存します。
    次のように実行権限を与え、引数を与えて実行してみます。

    $ chmod +x lsre.sh
    $ ./lsre.sh .
    ./lsre.sh
    ./test_dir
    $ ./lsre.sh ./test_dir
    ./test_dir/alnum
    ./test_dir/*asterisk
    ./test_dir/@atmark
    ./test_dir/+plus
    ./test_dir/日本語
    

    ファイル名だけを抽出

    basename というコマンドを使う手もありますが、bash には変数展開という便利なものがあるのでこれを利用します。
    注意点としては、変数展開は正規表現ではなくてワイルドカードになります。

    #!/bin/bash
    
    for path in "$1"/*; do
            filename="${path##*/}"
            echo "$filename"
    done
    

    変数展開の前方除去(最長一致) ##

    ここでの変数展開には前方除去(最長一致)の ## を使って パターン */ を指定していますが、考え方としては次のようになります。

    例としてパス /foo/bar/hoge を考えます。
    ワイルドカードでパターン */ にマッチする部分は次の通り3つあります。

    • /
    • /foo/
    • /foo/bar/

    この内、最長のものは /foo/bar/ なので、その部分を除いたファイル名 hoge が得られます。

    他にも後方除去や最短一致などの便利な変数展開があります。
    詳しくは参考に挙げたページをご覧ください。

    ファイル名が ASCII 文字だけで構成されているかの判定

    本題です。
    ここで前提として、正規表現は実装によって微妙に使える表現に違いがあります。
    これが今回の落し穴でした。

    ASCII 文字は16進表記で表すと、0x00 から 0x7F までです。
    これをそのまま表せば次のように書けると思ったのです。

    ^[\x00-\x7F]+$
    

    これを先程のシェルスクリプトに入れて、ファイル名が ASCII 文字のみの場合だった時に “[ASCII]” と表示するようにしたのが次のものになります。

    #!/bin/bash
    
    ASCII_PATTERN="^[\x00-\x7F]+$"
    
    for path in "$1"/*; do
            filename=${path##*/}
            echo "$filename"
            if [[ "$filename" =~ "$ASCII_PATTERN" ]]; then
                    echo "[ASCII]"
            fi
    done
    

    実行してみると、”[ASCII]” は一つも表示されません。

    ここからいくつも試していたのですが、それを全部書いていたら無駄に長くなるので試してだめだったパターンだけを示します。

    • "^[\x00-\x7F]+$"
    • "^[\u0000-\u007F]+$"
    • "^[\u00-\u7F]+$"

    $'\x00' の記法もだめでした(上に書こうとしたらドル記号が2個あると数式になるようで書けない)。

    ちなみにサクラエディタは [\u00-\u7F]+ の記法を受け付けてくれました。

    悩んだ挙句に「POSIX クラス」を発見して、次の正規表現に行き着きました。

    "^[[:graph:][:space:][:cntrl:]]+$"
    

    これを先程のシェルスクリプトに加えます。

    #!/bin/bash
    
    ASCII_PATTERN="^[[:graph:][:space:][:cntrl:]]+$"
    
    for path in "$1"/*; do
            filename="${path##*/}"
            echo "${filename}"
            if [[ "$filename" =~ "$ASCII_PATTERN" ]]; then
                    echo "[ASCII]"
            fi
    done
    

    動作確認もうまくいった、と思ったら日本語のファイル名も ASCII 扱いになってしまいました。

    $ ./lsre ./test_dir
    alnum
    [ASCII]
    *asterisk
    [ASCII]
    @atmark
    [ASCII]
    +plus
    [ASCII]
    日本語
    [ASCII]
    

    UTF-8 をちゃんと扱えていないのかと思って最初の1文字を取得してみたのが次です。

    #!/bin/bash
    
    ASCII_PATTERN="^[[:graph:][:space:][:cntrl:]]+$"
    
    for path in "$1"/*; do
            filename="${path##*/}"
            echo "$filename"
            first="${filename:0:1}"
            echo "$first"
            if [[ "$filename" =~ "$ASCII_PATTERN" ]]; then
                    echo "[ASCII]"
            fi
    done
    
    $ ./lsre ./test_dir
    alnum
    a
    [ASCII]
    *asterisk
    *
    [ASCII]
    @atmark
    @
    [ASCII]
    +plus
    +
    [ASCII]
    日本語
    日
    [ASCII]
    

    1文字目はちゃんと取れているので UTF-8 としては扱えているようなのですが……。

    バイト数も考慮して ASCII と判別する

    最終的には上記の1文字目を wc -c して何バイトかを見て判断するようにしました。

    #!/bin/bash
    
    ASCII_PATTERN="^[[:graph:][:space:][:cntrl:]]+$"
    
    for path in "$1"/*; do
            filename="${path##*/}"
            echo "${filename}""
            if [[ "$filename" =~ "$ASCII_PATTERN" ]] && \
                     [ `echo -n "${filename:0:1}" | wc -c` == "1" ]; then
                    echo "[ASCII]"
            fi
    done
    
    $ ./lsre ./test_dir
    alnum
    [ASCII]
    *asterisk
    [ASCII]
    @atmark
    [ASCII]
    +plus
    [ASCII]
    日本語
    

    ようやく欲しいものが得られました。

    ちなみに [:punct:] も試してみましたが結果は同じだったので書いていません。

    まとめ

    • bash の正規表現だけで制御文字の範囲まで含めた ASCII 文字は判定できない
    • 文字が何バイトかを求めて併せ技で解決
    • bash で ASCII 文字判定するのは大変

    参考

  • docker-compose で imgur を使わない HackMD 環境を構築

    docker-compose で imgur を使わない HackMD 環境を構築

    HackMD(https://hackmd.io/) は Webブラウザで動く Markdown エディタです。

    HackMD の素晴しい特徴として複数のクライアントによるリアルタイム同時編集機能があります。1人でも Web ブラウザを複数起動させれば試せるのでぜひ実際に試してみていただきたいのですが、複数の編集がリアルタイムに画面に反映されるのは今までにない体験で、かなり楽しいです。使いどころを問われると答えに窮しますが、それはこれから探っていくとして……。

    この HackMD の OSS として CodiMD があります。CodiMD は公式が docker-compose.yaml を提供してくれているので自分でも簡単に環境構築を行えます。

    この際、一つだけ落し穴があって注意する必要があります。HackMD は編集画面上に画像をそのままペーストできるのですが、その時の画像の保存先がデフォルトで imgur なので Ctrl + V した瞬間に全世界に公開されてしまうのです。これはうっかり大惨事を引き起しかねません。

    docker-compose.yaml を数行変更するだけでこれを回避できるので、今回はその手順で環境を構築してみます。普段は Debian なのですが、今回は使っているサーバの都合で Ubuntu 18.04 LTS です。

    ubuntu@dev:~$ cat /etc/os-release | grep VERSION=
     VERSION="18.04.2 LTS (Bionic Beaver)"
    ubuntu@dev:~$ git clone https://github.com/hackmdio/docker-hackmd.git
     Cloning into 'docker-hackmd'…
     remote: Enumerating objects: 731, done.
     remote: Total 731 (delta 0), reused 0 (delta 0), pack-reused 731
     Receiving objects: 100% (731/731), 222.28 KiB | 702.00 KiB/s, done.
     Resolving deltas: 100% (343/343), done.

    docker-hackmd/docker-compose.yml を編集します。app の environment 下に CMD_IMAGE_UPLOAD_TYPE と CMD_DOMIN を追加します。CMD_IMAGE_UPLOAD_TYPE の指定だけではエラーを吐くらしいので CMD_DOMAIN も指定します。

    もう一つ、自分の場合は CMD_URL_ADDPORT = false を追加しました。これを入れないとすべての URL に :3000 が付加されてしまい、サイトの表示が崩れてしまいました。直接ポートを指定して使う場合は問題になりませんが、今回は Nginx によるリバースプロキシを経由してサブドメインで運用する予定なのでポートの指定は不要でした。

       - HMD_DB_URL=postgres://hackmd:hackmdpass@database:5432/hackmd
       - CMD_IMAGE_UPLOAD_TYPE = filesystem
       - CMD_DOMAIN = hmd.kuratsuki.net
       - CMD_URL_ADDPORT = false
     ports: 

    編集が完了したら docker-compose up して HackMD を起動します。

    ubuntu@dev:~/docker-hackmd$ sudo docker-compose up -d
     [sudo] password for ubuntu:
     Pulling database (postgres:9.6-alpine)…
     9.6-alpine: Pulling from library/postgres
     bdf0201b3a05: Pull complete
     365f27dc05d7: Pull complete
     bf541d40dfbc: Pull complete
     2042c4eafdd8: Pull complete
     925cd0367a3b: Pull complete
     47c141dded4c: Pull complete
     751c0956cd65: Pull complete
     a9a85eacf5bb: Pull complete
     ba84e9ab01c0: Pull complete
     Pulling app (hackmdio/hackmd:1.2.0)…
     1.2.0: Pulling from hackmdio/hackmd
     f189db1b88b3: Pull complete
     3d06cf2f1b5e: Pull complete
     687ebdda822c: Pull complete
     99119ca3f34e: Pull complete
     e771d6006054: Pull complete
     b0cc28d0be2c: Pull complete
     7225c154ac40: Pull complete
     7659da3c5093: Pull complete
     8138c3c27b70: Pull complete
     3ac8c3e5ade3: Pull complete
     b2c8cd9cc032: Pull complete
     53f21b305291: Pull complete
     bb60fd15638c: Pull complete
     6b6e9067f162: Pull complete
     Creating docker-hackmd_database_1 … done
     Creating docker-hackmd_app_1      … done

    起動したら http://CMD_DOMAINで指定した名前またはIPアドレス:3000/ を開きましょう。ここで CMD_DOMAIN とは異なる名前でアクセスすると画面が思いっきり崩れます。CSS を相対 URL 指定ではなく絶対 URL 指定しているようです。

    docker-compose up は初回だけで、2回目以降は docker-compose start/stop を使いましょう。環境を再構築したいときは docker-compose rm で一旦削除するだけです。docker-compose は便利ですね。

    参考

    • https://github.com/hackmdio/codimd/blob/master/README.md
  • Raspbian (Debian 9 Stretch) の SSH 有効化

    Raspbian (Debian 9 Stretch) の SSH 有効化

    user/password = pi/raspberry でログインして “sudo raspi-config” を実行します。

    “5 Interfacing Options” の “P2 SSH” を開いて “YES” を選択して完了です。