まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:github

久しぶりにウェブサービスを作ってみたので、紹介含めてブログにまとめました。アイデアそのものは2週間ほど前に思いついて脳内設計し、1週間くらい前から作ったり改良を加えたりして、この三連休でとりあえず公開できるレベルになったかな、という感じです。

作ったのはいわゆる CMS (=Contents Management System「コンテンツ管理システム」)です。特徴的な要素として GitHub Issues を使っている、という点が挙げられると思っています。要は CMS のコンテンツそのものは専用 DB などに格納するのではなく、GitHub のリポジトリ内のプロジェクト/課題管理ツールである GitHub Issues を使っています。多くのケースでは GitHub Issues はプロジェクトの課題を記録したり、課題をカテゴリーに分類したり、課題の担当者や解決目標時期を割り当てたり、スレッド形式のコメントを追加したりしながら、プロジェクト全体の進捗を管理することに使われることが多いと思いっています。一方で、この GitHub Issues の仕組み自体はプロジェクト管理だけに使うのはもったいないほど色んな機能が無料で提供されているとも考えることができます。今回作成した GHaC(GitHub as CMS)は GitHub API を使って GitHub のログインや GitHub Issues の取得を行い、CMS 寄りの UI で表示することで CMS として使ってみたら面白いのでは? という興味から生まれたものです。

なお、2022/10/10 の現時点では PC 向けの UI のみ提供しています。


【GHaC の使い方】
GHaC を使う場合はまずウェブブラウザで以下にアクセスします:
  https://ghac.me/

2022101001


GHaC の紹介と簡単な使い方が説明されています。以下、このページに書かれた内容と重複する箇所がありますが、こちらでも使い方を説明します。

GHaC は GitHub のリポジトリを参照して(そのリポジトリの Issues や Comments を取り出して)コンテンツを表示します。例えばサンプルとして dotnsf/sample-issues という公開リポジトリを見る場合を想定します。なお、このリポジトリはソースコードとしては何もコミットされていないので、リポジトリを直接参照すると初期状態のまま表示されます:

https://github.com/dotnsf/sample-issues の画面)
2022101002


ソースコードは空ですが、GitHub Issues としての情報はいくつかの Issues と、各 Issue にコメントがついていたり、いなかったりします:

https://github.com/dotnsf/sample-issues/issues の画面。4つの Open な Issues と、表示されていない1つの Closed Issue が登録されています。また Comments が付与されている Issues は右側に吹き出しマークと一緒に Comments の数が表示されています)
2022101003


GHaC でこのリポジトリを参照する場合は https://ghac.me/ に続けて対象リポジトリを追加した URL にアクセスします。このリポジトリ(dotnsf/sample-issues)の場合であれば https://ghac.me/dotnsf/sample-issues にアクセスします。以下のような画面になります:
2022101004


最初は認証されていない状態でアクセスすることになるので上記のようなログインを促すメッセージだけが表示されています。画面右上の「ログイン」ボタンで GitHub の OAuth 認証ページに移動し、GitHub アカウントを指定してログインします。初回のみ以下のような画面になるので右下緑の "Authorize dotnsf" ボタンをクリックしてください:
2022101005


ログインが成功すると以下のような画面に切り替わります。なお、このリポジトリの例(dotnsf/sample-issues)は公開リポジトリなので誰がログインしてもこのように表示されますが、非公開リポジトリの場合はログインしたユーザーの参照権限の可否によって表示されたり、されなかったりします:
2022101006


画面最上部右にはログインしたユーザーの(GitHub の)プロフィールアイコンが表示されます。このアイコンをクリックすると GitHub からログアウトして GHaC のトップページに戻ります。またその右には GitHub API を実行できる回数※の目安が円グラフで表示されています。このグラフ部分にマウスのカーソルを重ねると、リセットまでの残り API 実行回数と、次回リセットの日付時刻が表示されます:
2022101007



GitHub API は1時間で 5000 回実行できます。1つのリポジトリの Issues や Comments を取得するために実行する API の回数はリポジトリによって異なります。↑の例ではこのページを表示するために既に4回実行していて、2022/10/10 19:50:06 までにあと 4996 回実行できる、という内容が表示されています(2022/10/10 19:50:06 になると API 実行はリセットされて、新たに 5000 回利用できるようになります)。

また画面上部には指定したリポジトリ内の open な issues がテーブル形式で表示されています:
2022101008

(参考 もとのリポジトリの Issues の同じ該当部分)
2022101003


画面下部には各 Issues と、その Issue に Comments がついている場合は全ての Comments がスレッド形式で表示されています。画面をスクロールすれば全て見ることができますが、特定の Issue の内容を確認したい場合は上部テーブルの該当 Issue をクリックすると、その Issue のスレッドに移動します。

例えば「ラベル付き issue」(Comments 数は2)と書かれたこの部分をクリックすると、
2022101008


該当 Issue が表示されている箇所までスクロールして表示することができます:
2022101009


Issue 本文や Comments 内容はマークダウン記法で記述されており、GHaC でも同様に表示されます(mermaid 記法には対応していません)。マークダウンによるリッチテキストを含む Issues や Comments はリッチテキストが再現されて表示されます:
2022101010

(2022/10/13 追記)
mermaid 記法と MathJax 記法に対応しました。


また現時点では GHaC は参照専用のツールです。GitHub リポジトリ内の Issues や Comments を編集するにはこのリポジトリを編集する権限をもったユーザーでログインした後に、画面内の GitHub アイコン(オクトキャットアイコン)をクリックすると・・
2022101009


対象リポジトリの対象 Issue の GitHub 画面が別タブで表示されます。必要に応じて、この画面から Edit ボタンをクリックして内容を編集したり、Comments を追加したり、New issue ボタンから新しい Issue を追加してください(保存後に GHaC 画面をリロードすればすぐに反映されているはずです)。余談ですが GitHub Issues の編集画面はマークダウンで記述できるだけでなく、OS からのコピー&ペーストで画像を貼りつけることができるので、リッチテキストの編集がとても楽です:
2022101011


なお、GHaC はデフォルトでは指定されたリポジトリの Open な Issues だけを対象にこのような画面を提供します。全ての Issues やクローズされた Issues だけを対象としたい場合は URL の最後にそれぞれ state=all (全ての Issues)や state=close (クローズされた Issues)というパラメータを付けてアクセスすることで目的の Issues を変更することができます(実はこれ以外にも URL パラメータで対象 Issues を絞り込むことができるようにしていますが、GitHub API でのオプションと同じ仕様にしているので興味ある人は自分で調べてみてください):
2022101012

2022101013


【まとめ】
このように GitHub Issues をウェブコンテンツのように使うことができるようになるのが GHaC の魅力です。GitHub Issues 本来の使い方とは異なるので少し慣れが必要かもしれませんが、ある意味で GitHub を無料のデータ管理ができるヘッドレス CMS のように使うことができると思っています。

なおこの GHaC を使うことで、公開されている全ての GitHub リポジトリを対象にこのような UI で Issues や Comments を表示することができるようになります。ただ例えば node-red/node-red のような Issues が多く登録されているようなリポジトリに対して実行すると1回の表示に必要な API 実行回数も多くなり、結果として表示されるまでにかかる時間も長くなってしまいます。その点に注意の上でリポジトリを指定して実行してください:
2022101014


(表示まで1分近くかかりました・・)
2022101015


まだまだ不具合も見つかっており、今後はその対処が必要になることに加え、現在は Oracle Cloud の無料インスタンス1つを使って運用していることもあって決して潤沢なサーバー環境ではありません。いろいろ不便があるかもしれませんが、ある程度理解した上で(主に情報提供用の)コンテンツ公開サービスと考えると、アクセス権管理も含めて結構使い道あるサービスなのではないかと思っています。


しばらく今の形で公開するつもりなので是非いろいろ使っていただき、ご意見やご要望などあれば伺って今後のサービス向上に役立てていきたいと思っています。


自分で開発したウェブサービスの公開先として、最近は dokku で作った公開プライベート(=自分専用)クラウド環境が重宝しています。デプロイはソースコードを git clone(pull) して git push するだけ、という非常に簡略化されていることに加え、やはり「自分専用(=使いたいサブドメインを誰かに先に取られている心配がない)」という気持ちの余裕も以外と大きい気がしています。dokku の導入については過去の記事を参照してください:
dokku でプライベート PaaS 環境を構築する(1)
dokku でプライベート PaaS 環境を構築する(2)


今回は dokku のいわゆる CI/CD(Continuous Integration / Continuous Delivery) についてブログエントリを書きます。dokku が非常に便利であるが故に、ソースコードが更新されるたびに手作業で push するのは(大した手間ではないのですが)やはり面倒です。というわけで Git リポジトリへのプッシュをフックとする dokku のデプロイ自動化を実現する方法を調べてみました。特に今回紹介するのは GitHub Actions を使った方法で、ソースコードを GitHub リポジトリで管理している人であればちょっとした設定を追加するだけで CI が実現できます。なお、今回 CI/CD を設定する dokku サーバーは "yellowmix.net" というカスタムドメインを有効に設定してあって、"XXXXX.yellowmix.net" というホスト名でアプリケーションをデプロイできるように設定済みである、という前提で紹介します(手順3の中にこの設定に関わる部分があります)。


以下、その手順紹介です。


【手順1 パスフレーズなしの秘密鍵/公開鍵を作成・登録】
GitHub Actions 内でデプロイを自動実行するため、パスフレーズ無しで作られた秘密鍵が必要です。というわけで、パスフレーズ有りの秘密鍵を使っている場合は作成および登録の再実行が必要になります。

まずは秘密鍵と公開鍵を(再)作成します。dokku サーバーのシェルにログインして、以下のコマンドを実行します:
$ ssh-keygen

この後、秘密鍵を作成するパスを入力するよう促されます。デフォルトでは "~/.ssh/id_rsa" が指定されているはずです。変更する場合は正しいファイルパスを入力、デフォルト設定のままで問題なければそのまま Enter を入力します。

次にファイルのパスフレーズを入力するよう促されますが、今回はパスフレーズのない秘密鍵および公開鍵のペアを作りたいので、空のまま Enter を押して先に進めます。パスフレーズの指定が終わると指定されたパスで秘密鍵および公開鍵が作成されます。特に秘密鍵の内容は手順2でも必要になるので、 cat コマンドで一度中身を確認しておきます("-----BEGIN RSA PRIVATE KEY-----" で始まって "-----END RSA PRIVATE KEY-----" で終わる内容が表示されます):
$ cat ~/.ssh/id_rsa

秘密鍵が作れたら dokku に登録します。上述のセットアップを既に別の(パスフレーズ付きの)秘密鍵で実施済みで、別の秘密鍵が登録済みの場合は、以下のコマンドで一度削除しておきます:
$ sudo dokku ssh-keys:remove admin

改めて(再度)秘密鍵をファイルパスを指定して登録します:
$ cat ~/.ssh/id_rsa | sudo dokku ssh-keys:add admin

これで新しい(パスフレーズのない)秘密鍵が作成され、 dokku に登録されました。続いて GitHub のリポジトリ側で GitHub Actions 側の設定を行います。


【手順2 GitHub の対象アプリケーションに GitHub Actions 用の秘密鍵を設定】
GitHub リポジトリにコミットされたタイミングでデプロイを実行できるよう、対象アプリケーションの GitHub リポジトリに GitHub Actions を設定します。まずは GitHub の対象アプリケーションのページを開き、Settings を選択します(ユーザーの Settings ではなく、アプリケーションの Settings を開きます):
2022060901


CI ワークフローを定義する前に、ワークフロー内で使う秘密鍵の情報を先に登録しておきます。Settings ページの左ペインから Secrets - Actions を選択します:
2022060902


新規に秘密鍵を登録したいので、画面右上の "New repository secret" ボタンをクリックします:
2022060903


秘密鍵を登録する画面が表示されるので、NAME 欄に SSH_PRIVATE_KEY と入力し、Value 欄には手順1で作成した秘密鍵ファイルの内容("-----BEGIN RSA PRIVATE KEY-----" から "-----END RSA PRIVATE KEY-----" まで)をそのままコピー&ペーストして入力し、最後に "Add secret" ボタンをクリックします:
2022060904


以下のように表示されればリポジトリへの秘密鍵の登録は完了です:
2022060905



【手順3 対象アプリケーションに GitHub Actions を設定】
ここまでの準備ができていれば、後はコミット時(プッシュ時)の GitHub Actions を登録することで dokku への自動デプロイを行うことができるようになります。ソースコードに .github/workflows/ というフォルダを追加し、このフォルダ内に以下の内容の deploy.yml ファイルを追加します:
---
name: 'deploy'

# yamllint disable-line rule:truthy
on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Cloning repo
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Push to dokku
        uses: dokku/github-action@master
        with:
          # specify the `main` branch as the remote branch to push to
          branch: 'main'
          git_remote_url: 'ssh://dokku@yellowmix.net:22/hostname'
          ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}

このうち、青字部分(main と書かれた部分)は「どのブランチが更新された時に、どのブランチを対象にデプロイするか」を指定しています。今回は「 main ブランチ更新時に main ブランチの内容をデプロイ」したいので、このような設定にしています。master ブランチを使いたい場合はここを master に変更してください。 また赤字部分はデプロイ先のリポジトリ URL です。この例では "yellowmix.net" というドメインホストを使って https://hostname.yellowmix.net/ という URL でアプリケーションを公開するための設定にしています。この部分は皆さんの dokku 環境に合わせて変更する必要があるのでご注意ください。 加えて、最終行に手順2で設定した秘密鍵を使う指定がされています。秘密鍵は外部に漏れてはまずい情報なので、ハードコートや公開変数などは使わずに、このような形で登録済みのシークレット情報を参照するよう指定しています。

なお、この deploy.yml ファイルの中で dokku/github-action というリポジトリが指定されている箇所がありますが、これは dokku から提供されている GitHub Actions 連携用のコンテナイメージです。このコンテナイメージを使ってデプロイを行う、という作業内容になっています。同コンテナイメージのソースコードはこちらで提供されています:


ここまで用意できたら、対象のプロジェクトを GitHub にコミット&プッシュします(以下の例は main ブランチに直接コミットしている想定です):
$ git add .

$ git commit -m 'node v14'

$ git push origin main

プッシュが成功すると GitHub リポジトリで GitHub Actions が起動するはずです。起動の様子はリポジトリ内の "Actions" メニューから確認できます(画面は Actions が実行中の様子):
2022060906


実行が(成功か失敗かで)完了すると、その結果が表示されます(この例では緑マークが付いているので成功しています。失敗は赤):
2022060907


成功・失敗に関係なく、Actions 名部分をクリックすると、Actions の実行ログを見ることができます。特に失敗した場合などはこのログが原因を調べるヒントになっている可能性が高いので、失敗に終わった場合はまずログを参照することになると思います:
2022060908


GitHub Actions が成功していた場合、更新されたアプリケーションが指定 URL で稼働できているはずです。実際にアクセスして動作確認してみましょう:
2022060900


以上、GitHub Actions を使って dokku にアプリケーションを自動デプロイするための設定を紹介しました。dokku は専用のリポジトリに git push するだけでもデプロイが出来て便利ですが、一度 CI/CD の便利さを知ってしまうと「わざわざデプロイする手間が面倒」に感じられてしまうほど便利なので、今後 dokku を使って動かすアプリについては全てこの設定を有効にしてもいいかな、と感じています。


 

自作のマークダウン CMS を一週間ほど自分だけで使ってみて、まあまあな感じだったので一般公開します。

"My info" を略して "Mynfo" と名付けています。ソースコードは GitHub に公開してます。自分でも使ってみたい場合はフォークして使ってください(フォーク先は public でも private でも構いませんが、public の場合は GitHub 上のコンテンツが公開される点に注意してください):
https://github.com/dotnsf/mynfo


また、サンプルとして(皆さんには編集権限はありませんが)この公開ソースコードで作ったコンテンツサービスを以下で公開しています:
https://mynfo.herokuapp.com/


実際にみなさんもこの仕組みを使ってコンテンツサービスを公開する場合、必須ではありませんが heroku のアカウントを所有している場合は自分の GitHub リポジトリにコンテンツをコミットすることで CI/CD で最新コンテンツに更新できる仕組みがあります(しかもこの方法だとコンテンツの更新履歴が Git 側に残る、という大きなメリットがあります)。この機能を使う想定で設定方法を README.md 内で解説しているので、heroku アカウントと併せて使うことを推奨します(試しにローカルホストで動かしてみる、程度であれば不要です)。


【ローカルホストで動かしてみる】
試しにローカルホストで動かすには Node.js 環境が必要です。事前にセットアップしておきましょう。

フォークしたコードを git clone して、npm install してから node app で実行します:
$ git clone https://github.com/dotnsf/mynfo.git ("dotnsf" 部分はあなたの Github アカウント名に読み替えてください)

$ cd mynfo

$ npm install

$ node app

デフォルトでは 8080 番ポートで HTTP リクエストを待ち受けます。ウェブブラウザで localhost:8080 にアクセスします。以下のような画面が表示されれば起動成功です(初期状態では README.md にシンボリックリンクした md/index.md の内容が(マークダウンが HTML に変換されて)表示されています。英語で一通りの使い方が紹介されています):
2022021901

(↑皆さんが使うときには md/index.md と README.md とのシンボリックリンクは切っちゃって構いません)


画面右上のハンバーガーメニューをクリックすると、リポジトリ内の md/ フォルダ以下を対象フォルダとするファイル選択画面が表示されます(デフォルト状態では md/ フォルダ以下に about_markdown.md, index.md, sample.md の3つのマークダウンファイルが存在しており、それらが(.md 拡張子が省略された形で)一覧表示されています:
2022021902


試しに一番上の about_markdown を選択すると md/about_markdown.md ファイルの内容が HTML に変換されて表示されます:
2022021901


また初期画面で表示されている README.md の最初にも記述していますが、実行時の環境変数や settings.js ファイル内で値を指定することで挙動を変えることができます。例えばサービスを不特定多数の人に公開するのではなく、一部の人向けにクローズドな形で公開したい場合は環境変数 BASIC_USERNAME と BASIC_PASSWORD を指定して実行することでベーシック認証が有効になり、正しい ユーザー ID とパスワードを入力しないとコンテンツは見ることができません。

見栄えに関しては環境変数 CONTENTS_TITLE でサイトの題名(デフォルト値は "Mynfo")を、BOOTSTRAP_THEME で Bootstrap のテーマ(デフォルト値は "warning")を指定できます。例えば前者を ".NSF Mynfo"、後者を "info" に指定するとこのような見栄えになります:
2022021904


基本機能としてはこのような感じです。プロジェクトフォルダ内の md/ フォルダ内にあるマークダウン(.md)ファイルを参照することができる、というものです。ただこのサービス自体にはマークダウンの編集機能はありません。このままサーバー上で(普通に)公開する場合は、別途 md/ フォルダ内のマークダウンファイルを編集する仕組み(サーバー上で直接編集するなど)が必要です。

動くか動かないかでは動くのですが、個人的にはこのような使い方はあまり想定していません。あくまで後述のような、GitHub リポジトリと連携する使い方を想定して開発しています。


【heroku のデプロイパイプラインを使う】
サーバー上の .md ファイルを直接編集するような使い方は本来想定したものではありません。この Mynfo の最大の特徴は以下のような使い方ができることです:

・ローカルホストで(好きなマークダウンエディタで).md ファイルを編集して、
・Github にコミット&プッシュすると、
・デプロイパイプラインによって、サービス側のコンテンツも更新される

この流れを heroku を使って実現する方法も README.md に(英語で)記載していますが、このブログでは同じ内容を日本語で紹介します。

まず heroku のアカウントを作成します。heroku には複数種類のアカウントが存在していますが、ただ動かすだけであれば(容量や可用性などを考慮せずに、小規模1インスタンスだけで動かす前提であれば)無料プランで構いません。

※なお heroku の無料プランで作成するアプリは 30 分間アクセスがないと止まってしまい、次にアクセスした時に再起動してからアプリが稼働します(この場合、アプリ画面が表示されるまで再起動のぶん少し時間がかかります)。24 時間ずっと稼働状態をキープしたい場合は有料プランで運用することを推奨します。

そして heroku にログインし、画面右上から New - Create new app を選択します:



次にアプリケーション名(下図では "mynfo")を入力し、サービスを作成する地域(下図では "United States")を指定します。そして次に(Create app ではなく) Add to pipeline を選択します:



Choose a pipeline と書かれた箇所をクリックし、Create new pipeline を選択します:



そして作成するパイプラインの名称(下図では "mynfo-pipeline")を適当に入力し、デプロイ先のステージを指定します。例えば「一度ステージング環境にデプロイし、動作確認ができたらプロダクション環境へデプロイ」するようなケースも想定できますが、今回はそのままプロダクション環境へデプロイするシンプルな例を紹介します。というわけでここではパイプラインのデプロイ先として production を直接指定します。最後に Create app をクリックします:



これでパイプラインそのものは作成されましたが、まだ GitHub リポジトリとの連携ができていません。このまま作業を続けます。

デプロイ方法として GitHub 連携をしたいので Deployment Method に GitHub を選択します:



そして対象となる GitHub リポジトリ(クローンしたリポジトリ)を指定します。Connect to GitHub 欄で自分の GitHub アイコンを選択し、対象リポジトリ名(そのままであれば "mynfo")を入力して Search します:



すると該当の GitHub リポジトリが見つかるので、横にある Connect ボタンをクリックします:



これで GitHub との接続ができました。最後に自動デプロイのための対象ブランチを指定するので、このまま続けます:



Automatic Deploy 欄でパイプラインが参照する GitHub リポジトリのブランチを指定します。特にブランチを作っていない場合は main だけが選択できるので、この main が選択された状態で Enable Automatic Deploy ボタンをクリックします(別途ブランチを作成して、main 以外のブランチを指定することも可能です):



これでフォークした GitHub リポジトリの main ブランチに変更がコミットされると、パイプラインが動いて最新コードが heroku 上のアプリケーションとして https://(アプリ名).herokuapp.com/ という URL で動き出すようになります:



また、この時点で作成したパイプラインの状態を参照すると、GitHub リポジトリの指定したブランチを接続されて、更新時の自動デプロイが有効になっている旨を確認できます:



この状態で試しに1ファイルを追加して動作確認しています。ローカルホスト上で1ファイル追加して git コマンドでコミットしてもいいのですが、今回は GitHub のウェブ画面を使って追加コミットする例を紹介します。

まずはウェブブラウザで該当 GitHub リポジトリのページ(フォークしたリポジトリのページ)を開き、md フォルダを選択します:



ここからファイルを指定して編集することもできますが、今回は新しいファイルを1つ追加することにします。画面右の Add file ボタンをクリックして、表示されるメニューから Create new file を指定します:



GitHub リポジトリに直接ファイルを追加する編集画面が表示されるので、試しにファイル名を test.md 、ファイルの中身には "# テスト" と見出し行だけ入力します:



画面を下にスクロールすると、GitHub に直接コミットする情報を指定する箇所が現れます。ここでコミットコメント(下図では "md/test.md added.")、を指定し、"Commit directly to the main branch" が選択された状態のまま Commit new file ボタンをクリックします:



これで GitHub リポジトリにマークダウンファイルが1つ追加でコミットされました(ローカルホストでも編集する場合はこの状態を git pull して、ローカルホスト側も更新しておいてください)。

そして指定ブランチがコミットされたことで、heroku に作成したパイプラインが自動実行されます。パイプライン実行そのものも数秒で終わってしまいますが、下図はビルド中(デプロイ完了前)のパイプライン画面です。アプリケーションのビルド中である旨が表示されています:



デプロイが完了するとこのような画面になり、ここから最新の(追加コードが反映された状態の)アプリケーションを開くこともできます:



確認のため、アプリケーションを開きます(Open app と書かれたボタンで開きます)。そして画面右上のハンバーガーメニューをクリックします:



すると存在していなかった test.md というファイルが確認できます。試しにこのファイルを選択します:



作成した通りの内容( "# テスト")であることが確認できます:



このように GitHub リポジトリと連携して、ローカルホストや GitHub 画面でマークダウンファイルを追加・更新・削除してサーバーにコミットすると、自動で公開アプリにも反映される仕組みができました。こうしておくことでローカルホストでは VSCode など好きなマークダウンエディタで編集することができるようになって、後は編集後にコミット&プッシュすればアプリにも反映される、という仕組みになりました。

冒頭でも書きましたが、このコンテンツサービスそのものはベーシック認証を有効にすることで一部の人だけに公開することもできます。ただそれとは別に GitHub リポジトリを公開するか/しないかを考慮する点があることに注意してください。基本的にベーシック認証をかけるサービスであれば、フォーク先の GitHub リポジトリも private 扱いにするべきだと思っています。

また環境変数 GITHUB_REPO_URL (リポジトリURL)と GITHUB_BRANCH (ブランチ名)を指定して実行すると、コンテンツの各画面内に GitHub のコンテンツメニューが追加され、各ページやファイル選択画面内から GitHub の対象ページ画面に移動したり、直接編集ページに移動したり、フォルダに新しいファイルを追加することも可能です(編集は編集権限を持っているユーザーのみ使えます):
2022021906

2022021905


この GitHub と連携した使い方はちょっとクセがあるので、普段 Git を使っていない人からすると(編集画面も無いし)むしろ不便に感じるかもしれません。が、Git やマークダウンに慣れたエンジニアであれば、むしろこの使い方で(慣れたマークダウンエディタとも連携して)コンテンツを更新情報まで含めて管理ができることになって便利だと思っていて、このようなサービスを作ってみました。

ちなみに、このアプリでブログ的な使い方をする場合は、コンテンツのファイル名を日付(例えば 20220219.md)にして、環境変数 REVERSE_FILES の値を 1 にして実行することで(新しい日付のファイルがリストの上にくるように並ぶので)使いやすいのでは、と思っています。



Git やマークダウンをバッチリ使っている自分からはなかなか便利に使えているサービスなので、同様の属性を持った人に使ってみていただきたいです。

なお現時点では GitHub と heroku のパイプラインを使う想定で README.md を記述していますが、GitLab など GitHub 以外の Git システムや、GitHub Actions など heroku パイプライン以外の自動デプロイ化の仕組みを使って運用することもできると考えています。ぜひ多くのパターンで挑戦していただきたいです。

2022 年最初のブログエントリとなります。遅ればせながら本年もよろしくお願いいたします。

今年最初のエントリは React や Angular といったフロントエンドフレームワークにまつわるネタです。最近流行りのこれらのフロントエンドフレームワークを使うことで、比較的簡単に SPA(Single Page Application) を作ることができます。SPA 化のメリット/デメリットを理解した上で作る必要があるとは思いますが、SPA 化することによる大きなメリットの1つに「Amazon S3 などのオブジェクトストレージや Github ページといった、安価かつ滅多にサービスダウンしない環境でフロントエンドのウェブホスティングができる」ことがあると思っています。

この点を少し補足しておきます。「自分が作って管理しているサービスやアプリケーションを安定運用したい」というのは誰でも思うことだと思っています。ただ現実にはこれは簡単なことではありません。サービスやアプリケーションは利用者が直接参照するフロントエンド部分に加えて、データベースなどのバックエンド部分、そしてこれらを繋ぐ API サーバーなどから成り立っていて、これら全てを安定運用するのは簡単なことではありません。特にフロントエンドや API サーバーについては最近よく耳にするコンテナオーケストレーションなどによって比較的安価に安定運用することもできるようになりました。ただフロントエンド部がアプリケーションサーバーを持たないシンプルな静的ウェブコンテンツであれば、上述したような Amazon S3 のウェブコンテンツ機能を使ったり、Github ページ機能を使うことで、滅多にサービスダウンしないウェブページとして公開するという方法もあります。現実問題として、この方法だとフロントエンドページの公開は簡単ですが、API サーバーなどのバックエンドとの通信時に CORS を考慮する必要が生じたりして、これはこれで面倒な設定も必要になるのですが、一方で「ほぼ無料」レベルの安価で利用できるウェブサーバーにしてはかなり安定しているのも事実で、用途によっては(「面倒な設定」の面倒レベルがあまり高くならないケースであれば)運用方法の考慮に含めてもいいのではないかと思っています。補足は以上。


一方、最近のウェブアプリケーション開発において IDaaS の利用ケースもよくあります(個人的にも業務で多く使っている印象です)。ログインや認証、ユーザー管理といった ID 周りの機能がマネージドサービスとして提供されていて、自分で面倒な開発をしなくても API や SDK から利用可能になる、というものです。具体的なサービスとしては AWS CognitoAuth0 などが有名どころでしょうか。IBM Cloud からも AppID という IDaaS が提供されています。

そしてフロントエンドアプリケーションと IDaaS の組み合わせというのが実は相性が悪かったりします(苦笑)。多くの IDaaS では OAuth と呼ばれるプロトコルでの認証/認可が主流となっているのですが、アプリケーションサーバーを持たない静的なフロントエンドアプリケーションでは OAuth のコールバックの仕組みとの相性が悪く、実装が非常に難しいという事情があります。その結果として、各 IDaaS ベンダーから認証専用の JavaScript SDK が提供され、それらを使って認証機能を実装することになります。IBM Cloud の AppID も専用の JavaScript SDK が用意され、React などで作ったフロントエンドに組み込むことで AppID へのログインや認証を実現できます(この SDK では PKCE と呼ばれる方法を使ってログインを実現しています)。

で、ここまでのアプリ開発方法に関する内容についてはこちらのページでも紹介されているのですが、問題はこの先にありました。この方法で作った React のフロントエンドアプリを Github ページにホスティングして運用しようとすると・・・結論としては少し特殊なリダイレクト URI の設定が必要でした。これを知らないと Github ページでの運用時にトラブルが起こりそうだと思ったので、将来の自分への備忘録の意味も含めてブログに設定内容を記載しておくことにしました。

前段部分の長いブログですが、といった背景の中で「IBM AppID の JavaScript SDK を使って作った SPA アプリを Github ページで動かす」ためのアプリ開発手順と設定内容について、順に紹介します。


【React で SPA アプリを作成する】
この部分は上述ページでも紹介されている内容ではありますが、一般的な内容でもあるので、特にコメントに色をつけずに紹介します。以下のコマンドで react-appid というフォルダに React のサンプルアプリを作成します:
$ npx create-react-app react-appid

$ cd react-appid

ここまでは普通の React アプリと同じです。ここから下で AppID の認証に対応したり、Github ページで運用する場合特有の設定を加えていきます。


【IBM AppID サービスインスタンスの準備を行う】
ここは上述ページでも触れられてはいるのですが、あまり詳しくは紹介されていないので、ここで改めて手順を紹介します。

IBM Cloud にログインして AppID サービスを作成後にインスタンスを開き、「アプリケーション」タブから「アプリケーションの追加」ボタンで対象アプリケーションを追加します。その際にアプリケーションのタイプを「単一ページ・アプリケーション」(SPA) として追加するようにしてください:
2022011301


追加したアプリケーションを選択して、内容を確認します。type の値が "singlepageapp" となっていることを確認してください。確認できたらこの中の "clientId" 値と "discoveryEndpoint" 値を後で使うことになるのでメモしておきます:
2022011303


まだ AppID にユーザーを登録していない場合はここでユーザーも登録しておきましょう。メニューの「クラウド・ディレクトリー」から「ユーザー」を選択し、「ユーザーの追加」からログイン可能なユーザー(の ID とパスワード)を登録しておきます:
2022011302


またリダイレクト URL を登録しておきましょう。「認証の管理」から「認証設定」を選択して、リダイレクト URL に http://localhost:3000/ を追加しておきます:
2022011303



IBM AppID 側で準備する内容は以上です。取得した情報を使ってアプリ開発を続けます。


【React アプリに IBM AppID モジュールを追加して、AppID のログイン機能を追加する】
ここは上述ページでも詳しく記載されている内容です。まずは IBM AppID を利用するためのライブラリを追加します:
$ npm install ibmcloud-appid-js

そしてソースコードの src/App.js をテキストエディタで開き、以下の内容に書き換えます(詳しい内容は上述ページを参照してください)。この時に太字で示している clientId の値と discoveryEndpoint の値には先程 AppID の画面で確認した clientId 値と discoveryEndpoint 値をコピペして指定してください:
import React from 'react';
import logo from './logo.svg';
import './App.css';
import AppID from 'ibmcloud-appid-js';
function App() {
  const appID = React.useMemo(() => {
    return new AppID()
  }, []);
  const [errorState, setErrorState] = React.useState(false);
  const [errorMessage, setErrorMessage] = React.useState('');
  (async () => {
    try {
      await appID.init({
        clientId: 'AppID の clientID の値',
        discoveryEndpoint: 'AppID の discoveryEndpoint の値'
      });
    } catch (e) {
      setErrorState(true);
      setErrorMessage(e.message);
    }
  })();
  const [welcomeDisplayState, setWelcomeDisplayState] = React.useState(false);
  const [loginButtonDisplayState, setLoginButtonDisplayState] = React.useState(true);
  const [userName, setUserName] = React.useState('');
  const loginAction = async () => {
    try {
      const tokens = await appID.signin();
      setErrorState(false);
      setLoginButtonDisplayState(false);
      setWelcomeDisplayState(true);
      setUserName(tokens.idTokenPayload.name);
    } catch (e) {
      setErrorState(true);
      setErrorMessage(e.message);
    }
  };
  return (
    <div  classname="App">
    <header  classname="App-header">
      <img  alt="logo" classname="App-logo" src="{logo}">
        {welcomeDisplayState && <div> Welcome {userName}! You are now authenticated.</div>}
        {loginButtonDisplayState && <button  onclick="{loginAction}" id="login" style="{{fontSize:">Login</button>}
        {errorState && <div  style="{{color:">{errorMessage}</div>}
    </header>
    </div>
  );
}
export default App;

この状態で一度動作確認します:
$ npm start

ウェブブラウザが起動して http://localhost:3000/ が開き、以下のような画面が表示されます:
2022011301


"Login" と書かれた箇所をクリックするとログイン画面がポップアップ表示されます。ここで AppID に登録済みのユーザー ID とパスワードを指定してサインインします:
2022011304


正しくログインできると先程の画面に戻り、登録したユーザーの氏名が表示とともに "You are now authenticated." と表示され、ログインが成功したことが明示されます:
2022011305


【React アプリをビルドして、SPA アプリでもログインできることを確認する】
今作成した React アプリをビルドして index.html にまとめた SPA アプリにしたあとでも AppID でログインできることを確認します。nginx などの HTTP サーバーがあればそれを使ってもいいのですが、ここでは Node.js のシンプルなウェブサーバーを使って動作確認します。以下のような内容で app.js を作成し、react-appid フォルダ直下(README.md などと同じ階層)に保存します:
var express = require( 'express' ),
    app = express();

app.use( express.static( __dirname + '/build' ) );

var port = process.env.PORT || 3000;
app.listen( port );
console.log( "server starting on " + port + " ..." );

↑ build/ フォルダをコンテキストルートにして 3000 番ポートで HTTP リクエストを待ち受けるだけのシンプルなコードです。

app.js が準備できたら、まずは一度 React コードをビルドして SPA アプリ化します:
$ npm run build

ビルドが完了すると React アプリが SPA 化されて圧縮されて build/ フォルダにまとめられますので、これを app.js を使って起動します:
$ node app

改めてウェブブラウザで http://localhost:3000/ にアクセスして同じアプリが起動していることを確認し、Login から AppID アカウントでログインできることを確認します。アプリケーション・サーバーを持たない SPA アプリでも IBM AppID を使って認証できることが確認できました:
2022011305


【React アプリをビルドした SPA アプリを Github ページでも動くように調整する】
ここまでできれば後は build/ フォルダを Github ページで公開するだけで動くんじゃないか? と思うかもしれませんが、実は期待通りに動くようになるまではいくつかの落とし穴があります。1つずつ解いていきましょう。

(1)絶対パス→相対パスへの書き換え

React の SPA アプリをただビルドしただけだと、コンテキストルート直下で動く想定のアプリになってしまいます。どういうことかというと、例えば http://localhost:3000/ とか https://xxx.xxxxxxx.com/ といったサブディレクトリを持たない GET / というリクエストに対して動くアプリになります(要はビルドで作成される index.html 内で参照される外部 CSS や JavaScript は "/" ではじまる絶対パスになっています)。一方、Github ページで動かす際は https://dotnsf.github.io/react-appid/ のようなサブディレクトリ上で動くことになります。ここがコンフリクトになってしまい、絶対パスで指定された CSS や JavaScript のロードエラーが発生してしまうのでした。これを回避するために index.html 内の該当箇所を絶対パス指定から相対パス指定に変更する必要があるのでした。

具体的にはこの下の(3)までの作業後に改めて $ npm run build を実行してから、build/index.html の以下の <head> 部分で頭に . を付けて、絶対パス指定から相対パス指定に書き換えてください:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" href="./favicon.ico"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="theme-color" content="#000000"/>
<meta name="description" content="Web site created using create-react-app"/>
<link rel="apple-touch-icon" href="./logo192.png"/>
<link rel="manifest" href="./manifest.json"/>
<title>React App</title>
<script defer="defer" src="./static/js/main.85d3d620.js"></script>
<link href="./static/css/main.073c9b0a.css" rel="stylesheet">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>



(2)回転する画像の URL を絶対指定

(1)と似た内容ですが、SPA アプリ内で表示される画像も絶対パスのままだと正しく表示されません。ただこちらは JavaScript 内で {logo} と指定されている画像なので単純に絶対パスを相対パスにすればよいという対処が使えないのでした。 まあアプリケーションとしては必須ではないので単純に画像ごと削除してしまってもいいのですが、残したい場合は Git リポジトリ上の画像 URL を直接指定する、などの方法で対処する必要があります。よくわからない人は以下の例でそのまま書き換えてください:
    :
  return (
    <div className='App'>
    <header className='App-header'>
      <img src="https://raw.githubusercontent.com/dotnsf/react-appid/main/src/logo.svg" className='App-logo' alt='logo' />
        {welcomeDisplayState && <div> Welcome {userName}! You are now authenticated.</div>}
        {loginButtonDisplayState && <button style={{fontSize: '24px', backgroundColor: 'skyblue', 
          border: 'none', cursor: 'pointer'}} id='login' onClick={loginAction}>Login</button>}
        {errorState && <div style={{color: 'red'}}>{errorMessage}</div>}
    </header>
    </div>
  );
    :


(3)デフォルトの .gitignore を変更

$ npx create-react-app コマンドで作成した React プロジェクトにははじめから .gitignore ファイルが含まれています。が、この .gitignore では /build フォルダを除外するよう記述されています。今回は build/ フォルダを Github ページで運用することが目的なので、/build フォルダが除外されてしまっては意味がありません。.gitignore ファイルを編集して、/build フォルダを含めるよう(コメントアウトするなど)してください:
  :
  :
#/build
  :
  :

(1)でも紹介しましたが、ここまでの変更を行ったら再度 SPA アプリケーションをビルドし、その後に build/index.html ファイルに対して(1)の変更を行ってください。


(4)AppID のリダイレクト URL の設定が特殊

これまではアプリケーションを http://localhost:3000/ という開発時専用の URL で実行していたので、AppID のリダイレクト URL も http://localhost:3000 だけを登録して動作確認できました。では実際に Github ページでアプリケーションを動かす際にはどのようなリダイレクト URL を指定すればいいのでしょうか? 実はここがくせ者でした。

例えば私(Github ID は dotnsf )が react-appid という github リポジトリを作って、このリポジトリを Github ページとして公開して運用しようとすると、アプリケーションの URL は以下のようになります:
 https://dotnsf.github.io/react-appid

2022011306


ということは AppID に設定するリダイレクト URL にもこの値を指定するべき・・・だと思っていたのですが、なんとリポジトリ名部分が不要で、 https://dotnsf.github.io/ を指定するのが正しい設定内容のようでした:
2022011308


この URL がリダイレクト URL として設定されていれば AppID SDK が正しく動作して、認証も正しく実行できるようになりました(自分は実はここで結構つまずきました):
2022011307


(5)build フォルダを Github ページとして公開する方法
最後に作成したプロジェクトを Github に登録して、build フォルダを Github ページで公開する方法についてです。root フォルダや docs フォルダを Github ページで公開する場合は選択肢から選ぶだけなんですが、任意のフォルダを Github ページで公開するのは少しコツが必要です。

例えば react-appid というリポジトリを使う場合は、まず Github 上でこのリポジトリを公開設定で作成します。そして普通に main リポジトリに登録します:
$ git init

$ git add .

$ git commit -m 'first commit'

$ git branch -M main

$ git remote add origin https://github.com/dotnsf/react-appid.git

$ git push -u origin main

その後、build フォルダを Github ページで公開するには以下のコマンドを実行します:
$ git subtree push --prefix build origin gh-pages

これで該当フォルダが Github ページとして公開されます。実際に以下のページはこの設定を使って公開しています:

https://dotnsf.github.io/react-appid/

2022011306


ID: username1@test.com, パスワード: password1 でログインできるので、よければ試してみてください:
2022011307


React で作成した SPA アプリケーションの認証をどうするか、という問題を IBM AppID (と SDK)で解決する方法と、ビルドした SPA アプリを Github ページで運用する場合の特殊な設定やコマンドについて紹介しました。




QR コード生成サービスを作ってソースコードごと公開してみました:
https://dotnsf.github.io/qrgenerator/

2021091203



同じようなコンセプトのサービスは世に多くあることを知っていますが、自分が作ったものは以下のような特徴を持ってます。便利に感じた方はぜひ使ってみてください。
  • (静的コンテンツだけで構成される)Github ページで公開している。バックエンドサービスが使えない形で公開しているので、実は裏で URL を集めている、といった心配は無用
  • 単に情報を QR コード化するだけのサービスではなく、考えつく限りの以下のようなカスタマイズを可能にしている

カスタマイズできる内容は以下の通りです:
・内容(テキスト、URLなど ここはカスタマイズできて当然)
誤り訂正レベル(デフォルトは M(約15%))
・生成する画像のサイズ(デフォルトは 512x512)
・前景色(デフォルト値は黒)
・背景色(デフォルト値は白)
・画面中央にロゴのように含める画像(デフォルトなし 誤り訂正レベルに応じて自動拡大縮小)


たとえばこんな感じにマンホールマップの URL と画像を設定して「生成」をクリックすると、
2021091201


指定された通りの QR コードがこんな感じで作成されて、
2021091202


「画像ダウンロード」をクリックすると、生成された QR コードを画像としてダウンロードできます:
1631428093716

細かい仕様としては、生成された QR コード部分をクリックすると情報テキストがクリップボードにコピーされます。上の例だと "https://manholemap.juge.me/" がクリップボードにコピーされるので、ブラウザのアドレス欄にペーストするだけでマンホールマップに遷移できるようになる、というわけです。


カスタマイズできる内容のうち、誤り訂正レベルについては自分も知らなかったので解説を加えておきます。

QR コードは紙やビニールといった「クシャクシャになったりして、そもそも印刷内容が平らに見えない可能性がある媒体に印刷して使う」可能性があることを考慮した仕様になっています。それが誤り訂正レベルです。QR コード自体を冗長化する形で作成して、画面の一部が読み取り不可な状態になっていても含まれている情報を認識できるようにするためのものです。

これが誤り訂正レベルで、QR コードでは4段階(H(約30%), Q(約25%), M(約15%), L(約7%))用意されています。H だと全体の30%が読み取り不可になっていても大丈夫、という意味ですが、QR コード自体が他よりも細かなものになります。ちなみに以下はどちらも "https://manholemap.juge.me/" の QR コードですが、左は誤り訂正レベル H 、右は Q です。白黒の細かさの違いがわかると思います:
Q


そのため最終的な印字サイズが小さいと読み取り自体が難しくなったりします。どの誤り訂正レベルを選ぶかは(真ん中にロゴ画像を含めるかどうかにも関わりますが)試行錯誤しながら読み取れそうなものを作りながら選ぶ必要があると思っています。

ちなみにソースコードはこちらです。裏で URL を保存してたりしない証拠としてこちらも公開しておきます:
https://github.com/dotnsf/qrgenerator


よかったら使ってください。


このページのトップヘ