IssoとDockerでHugoのコメント機能を実現する

Hugoはデフォルトでコメント機能を持っていない。 公式ドキュメントにはDisqusを使ってコメント機能を実装する方法などが紹介されているが、有料だったり広告が出たりであまり評判は良くないようだ。 色々調べたところIssoというPython製のコメント用サーバを使うことで、無料かつ広告なしでコメント機能を実現できると分かった。 今回はその方法をご紹介。 Issoとは IssoとはPython製のコメントサーバである。 SaaS系のコメントツールと違って自前でサーバを動かす環境を用意するという手間はかかるが、その反面Hugoに限らずあらゆるwebコンテンツにコメント機能を実装することができる。 機能に関してもメールでのコメント通知、連投防止、管理画面など一通りのものは揃っている。 環境 Hugo 0.145.0 PaperMod 8.0 IssoのDockerイメージ latest(2025-04-16時点) 方法 0. 前提 今回はHugoで作成したブログをhttps://blog.zurukumo.devに、Issoのコメントサーバをhttps://blog-api.zurukumo.devにデプロイすることを想定している。 ブログはVercel上にデプロイし、コメントサーバはさくらのVPSにコンテナデプロイした。 1. Issoでコメントサーバを作成する コメントサーバの構成は以下の通り。 . ├── config │ └── isso.cfg ├── db │ └── comments.db └── docker-compose.yml └── .env config/isso.cfgにIssoの設定を書き、docker-compose.ymlにコンテナの構成を書く。 db/comments.dbはコンテナを立ち上げると自動で作成されるので、用意するのはdbディレクトリだけでOK。 docker-compose.ymlは以下のように書いた。 docker-compose.yml services: isso: image: ghcr.io/isso-comments/isso:latest env_file: .env ports: - "8080:8080" volumes: - ./config:/config - ./db:/db Issoは公式がDockerイメージを配布してくれているので、それを使えば上記のような簡素な設定で問題なく動く。 次にconfig/isso.cfgにIssoの設定を書く。 config/isso.cfg [general] dbpath = /db/comments.db host = https://blog.zurukumo.dev notify = smtp [server] listen = http://localhost:8080/ [moderation] enabled=false [smtp] username = (自分のメールアドレス) password = ${SMTP_PASSWORD} host = smtp.gmail.com port = 587 security = starttls to = (自分のメールアドレス) from = "isso comments" <(自分のメールアドレス)> timeout = 10 [guard] enabled = true ratelimit = 2 direct-reply = 3 reply-to-self = false require-author = false require-email = false [hash] salt = ${HASH_SALT} algorithm = pbkdf2 [admin] enabled = true password = ${ADMIN_PASSWORD} Server Configulationを参考にしながら書いた。 ...

2025-04-16

nginxのリバースプロキシを使って1つのVPSで複数のサービスを運営する

やりたいこと 自分はさくらのVPSを借りて複数のサービスを運営している。 本当なら1つのサービスに対して1台のVPSを契約するのが理想だが、趣味の開発なのでそこまでお金はかけられない。 なので1台のVPS上に複数のコンテナを立ち上げて、nginxのリバースプロキシを使ってアクセスを振り分けるという形式を採用している。 例えばhttps://a.zurukumo.devへのアクセスはコンテナAに、https://b.zurukumo.devへのアクセスはコンテナBに振り分けるというような感じだ。 nginxの設定もSSL証明書の発行も手動で実行していたが、だんだんめんどくさくなってきたので、もっと楽にできる方法がないか調べてみた。 そこで発見したのがnginx-proxyとacme-companionというDockerイメージだった。 この2つのイメージを組み合わせることで、最小限の設定でリバースプロキシの設定からSSL証明書の発行までを自動化することができた。 今回の記事はその方法のご紹介である。 方法 まず、ざっくりとした構成を仮定する。 kagoとnbamashという2つのサービスを運営していて、どちらのサービスもwebコンテナ + dbコンテナという構成である。 両サービスともdocker composeで立ち上げているとする。 ここでhttps://kago-api.zurukumo.devへのアクセスはkagoのwebコンテナに、https://nbamash-api.zurukumo.devへのアクセスはnbamashのwebコンテナに振り分けることを目標にする。 1. DNSに利用したいドメインを登録しておく 向き先はVPSのIPアドレスにする。 自分の場合はこんな感じ(黒塗りの部分は両方同じIPアドレス)。 2. ネットワークを作成する nginx-proxyとacme-companionは動いてるDockerコンテナを検知すると自動でプロキシの設定やSSL証明書の発行を行ってくれる。 動いているコンテナを検知できるようにするるために、nginx-proxyとacme-companionを各種サービスと接続するためのネットワークを作成する。 docker network create proxy 今回はproxyという名前のネットワークを作成したが、名前はご自由に。 3. リバースプロキシ&SSL証明書取得用のdocker-compose.ymlを作成する 公式GitHubのDockerコマンドをdocker-compose.ymlに素直に書き換えた上で、networksの設定だけ追加した。 proxy/docker-compose.yml services: proxy: image: nginxproxy/nginx-proxy ports: - 80:80 - 443:443 volumes: - certs:/etc/nginx/certs - html:/usr/share/nginx/html - /var/run/docker.sock:/tmp/docker.sock:ro networks: - proxy cert: image: nginxproxy/acme-companion volumes_from: - proxy volumes: - acme:/etc/acme.sh - /var/run/docker.sock:/var/run/docker.sock:ro environment: - DEFAULT_EMAIL=(自分のメールアドレス) networks: - proxy volumes: certs: html: acme: networks: proxy: external: true HTTP-01 チャレンジに80番ポートを使用するため、80番ポートを開放している。 ...

2025-04-15

【書評】プロになるためのSpring入門

転職先の会社ではJavaを使うので、入社前にある程度Springの勉強をしようと思った。 初めはインターネットの情報を見ながらSpringの勉強をしていたが、かなりブラックボックスになっている部分が多いフレームワークだなという印象で、「これは書籍を使って体系的に勉強したほうが効率が良さそうだな」と感じた。 そこで評価が良さげだった『プロになるためのSpring入門』を購入。 #PR プロになるためのSpring入門 - ゼロからの開発力養成講座 楽天 Amazon 感想 かなり体系的に書かれていてSpringの全体像を把握するのに役立った。 MVCのような基本的な内容からデータベースアクセスの方法、Thymleafの使い方、Spring Securityの使い方のような少し高度な内容まで幅広くカバーされていて、入門書としてはこの一冊で十分事足りるのではないかと思う。 DIコンテナで何が行われているかの解説、Configが読み込まれる仕組みの解説、セッションの管理方法の解説など、フレームワークのブラックボックスになりがちな部分についても丁寧に解説されているのが好印象だった。 フレームワークの裏側を理解せずとも割となんとなくで開発自体はできてしまうものなのだが、裏側を理解しているかいないかでバグにハマってから解決するまでの速度に雲泥の差が生まれると思っているので、本書のように裏側部分も丁寧に解説してくれる技術書はとてもありがたい。 テストについてもかなり丁寧に解説されていて、各種テストの方法からモックの方法やテストデータの作成の方法まで幅広く触れられていた。 本書では二通りの方法で同一の機能を実現できる場合に、「こういう場面ではこういう理由で方法Aを、こういう場面ではこういう理由で方法Bを」というような書き方が頻繁になされていて、選択に迷いがちな初学者にとっては非常にありがたかった。 一方でJavaの文法やWebフレームワークの基礎的な部分などに関しては既知のものとして話が進んでいき、あまり詳細な説明はされなかった。 おそらく本書がターゲットにしている読者は、「ある程度Web開発の経験があって、かつ他の言語からJavaに乗り換えようとしているエンジニア」なのだと思う。 「Web開発初心者です」、「Java初心者です」というような人が初手で手を出す本としては正直少しハードルが高いのかなと思った。 まとめ ある程度Web開発の経験がある人がSpringを学ぶための本としてはかなり優れていると思う。 自分はターゲット層にドンピシャだったので満足度が高かった。

2025-04-14

Hugoのコードブロックに併せてファイル名を表示する

やりたいこと 以下のようにコードブロックと併せてファイル名も表示したい。 test.txt Hello, World! Hugoのデフォルトのコードブロックにはファイル名を表示する機能がない。 また自分の使っているPaperModというテーマでもサポートされていない。 そこで自分でテンプレートを拡張してコードブロックのファイル名を表示できるようにしてみた。 なお、使用しているテーマによっては拡張せずともファイル名を表示できる可能性があるので、まずはそちらの確認をすることをおすすめする。 環境 Hugo 0.145.0 PaperMod 8.0 テンプレートを作成 公式のGitHubにあるコードを参考にして作成した。 公式のコード 上記のコードではTailwindが使われていたが、自分のテーマではTailwindは使われていなかったので、Tailwindを使わずに.cssファイルを使ってスタイリングをする方式に変更した。 またcopy、details、open、summaryといった将来使わなそうな不要なオプションはメンテが面倒になりそうだったので削除した。 copyに関してはPaperModでコピー機能が実装されているので削除したが、使用しているテーマでコピー機能がサポートされていない場合は採用するのもありだと思う。 というわけで、以下のコードを/layouts/_default/_markup/render-codeblock.htmlとして保存する。 Hugoはコードブロックを見つけるとrender-codeblock.htmlを呼び出すと事前取り決めてあるので、ファイル名も配置場所も一文字でも間違えたらダメなことに注意。 /layouts/_default/_markup/render-codeblock.html {{- /* Forked from https://github.com/gohugoio/hugo/blob/master/docs/layouts/_default/_markup/render-codeblock.html At 2025-04-13 Use .css file instead of Tailwind Deleted copy, details, open, summary params */}} {{/* prettier-ignore-start */}} {{/* Renders a highlighted code block using the given options and attributes. In addition to the options available to the transform.Highlight function, you may also specify the following parameters: @param {string} [file] The file name to display above the rendered code. @returns {template.HTML} {{/* prettier-ignore-end */}} {{- $file := or .Attributes.file "" }} {{- $ext := strings.TrimPrefix "." (path.Ext $file) }} {{- $lang := or .Type $ext "text" }} {{- if in (slice "html" "gotmpl") $lang }} {{- $lang = "go-html-template" }} {{- end }} {{- if eq $lang "md" }} {{- $lang = "text" }} {{- end }} <div class="codeblock-wrapper"> {{- with $file }} <div class="codeblock-file"> {{ . }} </div> {{- end }} <div> {{- transform.Highlight (strings.TrimSpace .Inner) $lang .Options }} </div> </div> CSSファイルを作成 PaperMod用のスタイリングの一例が以下。 ...

2025-04-13

LOVEマシーンが好きだと言える人間になりたい

大学生の頃、モーニング娘。にハマっていた。 何をきっかけにハマったのかは覚えていないが、おそらくYouTubeでなんかのPVを見たのがきっかけだったんじゃないかと思う。 ハマってからは昔の曲から最近の曲まで色々な曲を聴き漁った。 歴史のあるアイドルグループなので曲数は膨大だったが、暇さえあればずっとモー娘。の曲を聴いていた。 モー娘。が好きだということは周りに公言していたので、何人かの知り合いは「一番好きな曲は何?」と尋ねてくれた。 その質問に対し自分は「『ここにいるぜぇ!』が一番好き」と答えていた。 『ここにいるぜぇ!』はとても良い曲だ。 曲調も歌詞も明るく、PVでもメンバーが元気いっぱいに飛び跳ねていて、それなのにどこか物悲しい雰囲気も感じられて、その雑多な感じが好きだった。 でも、実を言うと『ここにいるぜぇ!』は一番好きな曲ではなく二番目に好きな曲だった。 本当に一番好きな曲は『LOVEマシーン』だった。 Aメロ、Bメロ、サビ、全てのパートがキャッチーで完璧な曲だと思っている。 実際、CMやドラマなどのタイアップが無かったにも関わらずCD175万枚を売り上げたのだから、当時の世間の人達も「この曲良い曲だな」と感じたのだろうと思う。 でも、大学生のときの自分は「一番好きな曲は『LOVEマシーン』です」と答えることができなかった。 サザンオールスターズのファンが「『TSUNAMI』が一番好きです」なんて言うか? Mr.Childrenのファンが「『Tomorrow never knows』が一番好きです」なんて言うか? モー娘。のファンが「『LOVEマシーン』が一番好きです」なんて言うか? ...

2025-04-12

timeout付きsubprocess.runの途中までの出力を取得

問題 AtCoderのサンプルテスト用のCLIを作成している。 一部抜粋したコードが以下。 import subprocess (...中略...) result = subprocess.run( ["python3", f"contests/{args.contest}/{args.problem}.py"], input=input, capture_output=True, text=True, timeout=3, ) timeoutを指定することによって、AtCoderでいうところのTLE(Time Limit Exceeded)を実現している。 タイムアウトしても処理が打ち切られる寸前までの標準出力を使ってデバッグをしたいことがあったので、以下のようなコードを書いて標準出力の中身を取得していた。 しかしこのコードを実行すると標準出力の中身が空になることが何度かあった。 import subprocess (...中略...) try: result = subprocess.run( ["python3", f"contests/{args.contest}/{args.problem}.py"], input=input, capture_output=True, text=True, timeout=3, ) except subprocess.TimeoutExpired as error: print_yellow(f"TLE: Test {i}") print_yellow(error.stdout.decode()) # これがまれにNoneになる 解決策 Pythonの標準出力はコードが終了するかバッファがいっぱいになるまではバッファに溜め込まれてしまう。 つまり出力量が少なく、かつタイムアウトになるようなコードの出力は標準出力にフラッシュされることなく闇に葬られてしまう。 そこで以下のようにバッファに溜め込まないように-uオプションを付けることで解決。 result = subprocess.run( ["python3", "-u", f"contests/{args.contest}/{args.problem}.py"], input=input, capture_output=True, text=True, timeout=3, ) 参考 実際のコード - GitHub

2025-04-06

【書評】体系的に学ぶ 安全なWebアプリケーションの作り方 第2版

技術面接でボコボコにされた備忘録に書いた通り、先日おこなわれた技術面談でボコボコにされた。特にセキュリティ周りの質問に回答できなかったのが悔しかった。 そこでセキュリティの勉強をするために『体系的に学ぶ 安全なWebアプリケーションの作り方 第2版』なる書籍を読んだ。 せっかくなのでその書評をば。 #PR 体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 楽天 Amazon 良かった点 体系的、網羅的に学べる この書籍は667ページもあって、内容は体系的かつ網羅的に書かれている(と思う)。 まずWebアプリケーションのセキュリティに関するページは膨大で、これだけで400ページくらいある。 特にXSSやCSRFの説明はいろいろなパターンで何度も何度も出てくるので、自然と脳に定着すると思う。 自分は技術面接でCSRFのことを聞かれてクリックジャッキングのことを話してしまったが、もう今後間違うことはないだろう。 さらに、終盤では脆弱性診断の方法、ネットワークやマルウェアに関連する脆弱性、開発マネジメントの手法などについても触れられていて、かなり手広くカバーされているという印象。 一家に一台欲しいタイプの本だと思う。 サンプルが充実している 脆弱性を抱えるソースコードが大量に掲載されているので、脆弱性がどのようにして発生しているのかを理解しやすい。 加えてサンプルアプリケーション用のDocker環境も配布されているので、手元で気軽に脆弱性を体験できるのも良い。 微妙だった点 Docker環境のソースコードをいじれない 本書のサンプルはPHPで書かれているので、PHP初心者の自分的には「今このセッションの中身はどうなっているんだろう」みたいにデバッグしたくなることがあった。 ただDocker環境ではソースコードが編集できないようになっていたので(厳密には.tar.gzを展開した上でソースコードを編集し再圧縮した上でDockerイメージを再ビルドすれば編集できる)、理解が難しい点もあった。 PHPが得意な人にとっては問題ないかも。 認証周りの説明がやや少ない 例えばJWTやOAuthについての説明がほとんどなかった。 自分は仕事ではJWTやOAuthを利用することが多いので、もう少し詳しく知りたかった。 もし第3版が出るなら、ここらへんの説明をもう少し充実させてほしい。 まとめ 総じてかなり良い本だという印象。 上にも書いた通り、一家に一台欲しいタイプの本だと思う。 この本を読んでセキュリティに対するモチベーションが上がってきたので、秋にはセキュリティスペシャリストの試験も受けてみたい。

2025-04-02

更生した不良は偉くないと言う人がいるけど

更生した不良や犯罪者に対して「更生して偉い」と言ったら、「最初から真っ当に生きている人間の方が偉い」と説教してくる人をSNSではたまに見る。 でも「更生して偉い」という発言の裏で行われている比較って「不良」と「真っ当な人間」の比較じゃなくて、「更生した不良」と「更生していない不良」の比較だよな、多分。 だからそういう説教ってめちゃくちゃ的外れだと思う。 例えば運動会の徒競走で1位になった子どもに「偉いね」と褒めただけなのに、「いやボルトの方が何倍も速いし偉いだろ」と説教してくるのと本質的には変わらない。 ある地域、ある学校、ある徒競走の組の中で1位になったことを褒めているのであって、別にプロアスリートと比較して褒めているわけじゃないんだよ。 それと同じように、一度過ちを犯したら更生できない人がたくさんいる中で、それでも更生できた人のことを褒めているだけなんだよな。

2025-03-30

Hugoでtagの先頭文字が大文字になるのを回避する

Hugoはデフォルトだとフッターなどに表示されるtagの先頭文字が大文字になってしまう。 たとえばtags=['hoge']と設定すると、Hugoの.GetTermsで取得したデータはHogeになっている。 ちなみに.Data.Termsだとそのまま取得できるっぽい。 個人的にはtagsの値は入力そのままで表示したいのでこれを回避する方法を調べた。 方法 hugo.yamlに以下の設定を追加するだけ。 hugo.yaml titleCaseStyle: none 公式ドキュメントにもちゃんと書いてあるが、なかなか見つけられずにハマった。 デフォルトはapでnone以外にもchicagoとかgoとか色々ある。 ちなみに今回の記事はtagsの話として書いているが、categoriesに関しても同様の設定が適用されているはず(自分はcategoriesを使っていないので未確認)。

2025-03-27

tcardgenを使ってHugoでOGPを設定する

Go製のtcardgenというツールを使って、Hugoの各記事にOGP画像を設定する方法を紹介する。 今回実際に生成した画像は以下の通り。 tcardgenをインストールする tcardgenの公式ドキュメントを参考にインストールする。 今回はHomebrewを使ってインストールした。 brew install Ladicle/tap/tcardgen tcardgen用のディレクトリを作成する tcardgen用のフォントやテンプレートや設定ファイルはHugoのビルド時には不要なのでなるべくHugoからは切り離したい。 そこで今回はトップレベルに/tcardgenというディレクトリを作成してそこにtcardgenに関連するファイルをまとめた。 トップレベルのディレクトリ構成は以下の通り。 . ├── README.md ├── archetypes ├── assets ├── content ├── hugo.yaml ├── layouts ├── public ├── static ├── tcardgen └── themes フォントをダウンロードする tcardgenの公式ドキュメントを参考にして、均等フォントをダウンロードした。 今回はKintoSans-Bold.ttf、KintoSans-Medium.ttf、KintoSans-Regular.ttfだけを/tcardgen/fontsに放り込んだ。 後述する設定ファイルで他の種類のフォントを指定したい場合はそれらのフォントも同様に/tcardgen/fontsに放り込む。 テンプレートを作成する OGPのテンプレートを作成して、/tcardgen/template.pngとして保存した。 Canvaを使うと簡単にできた。 今回は以下のようなテンプレートを作成した。 tcardgenの設定ファイルを作成する フォントの色や場所などを変更するための設定ファイルを作成した。 今回は公式ドキュメントのtemplate3.config.yamlを参考にして以下のような設定ファイルを作成した。 /tcardgen/config.yaml template: config.yaml title: start: px: 125 py: 185 fgHexColor: "#000000" fontSize: 72 fontStyle: Bold maxWidth: 970 lineSpacing: 10 category: enabled: true start: px: 120 py: 110 fgHexColor: "#555555" fontSize: 42 fontStyle: Regular info: enabled: true start: px: 220 py: 480 fgHexColor: "#555555" fontSize: 38 fontStyle: Regular separator: " | " timeFormat: "2006-01-02" tags: enabled: true limit: 0 start: px: 1080 py: 110 fgHexColor: "#FFFFFF" bgHexColor: "#545454" fontSize: 32 fontStyle: Medium boxAlign: Right boxSpacing: 6 boxPadding: top: 6 right: 10 bottom: 6 left: 10 OGP生成用のシェルスクリプトを作成する 毎回tcardgenのコマンドを打つのは面倒なので、シェルスクリプトを作成して一発でOGP画像を生成できるようにする。 ...

2025-03-25