
[{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/ai/","section":"Tags","summary":"","title":"AI","type":"tags"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/ansible/","section":"Tags","summary":"","title":"Ansible","type":"tags"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/apache/","section":"Tags","summary":"","title":"Apache","type":"tags"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/certbot/","section":"Tags","summary":"","title":"Certbot","type":"tags"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/claude-code/","section":"Tags","summary":"","title":"Claude Code","type":"tags"},{"content":"このブログの記事作成フローは、以前の記事で紹介したように git commit・git push をトリガーにプレビューや本番反映が自動で流れる仕組みになっている。\nただ、毎回 Claude Code を開いて「プレビューして」「本番に反映して」と伝えるのは手間だ。記事を書くことだけに集中したい。 そこで Claude Code のカスタムスラッシュコマンドを使って、これらの操作をワンコマンド化した。\nカスタムスラッシュコマンドとは # Claude Code には、独自のスラッシュコマンドを作れる仕組みがある。所定の場所に Markdown ファイルを置くだけで、そのファイル名がそのままコマンド名になる。\nファイル名 = コマンド名 というシンプルなルールだ。\nたとえば preview.md というファイルを置けば /preview というコマンドが使えるようになり、そのファイルの中身がプロンプトとして Claude に渡される。コマンドを入力するだけで、ファイルに書いた指示を Claude が実行してくれる。\n配置場所とスコープ # コマンドファイルの置き場所によって有効範囲が変わる。\n配置場所 スコープ \u0026lt;プロジェクトルート\u0026gt;/.claude/commands/ そのプロジェクトのみ ~/.claude/commands/ 全プロジェクトで有効 プロジェクト固有の操作はプロジェクト側に置く。こうすると他のプロジェクトで Claude を使っても同名のコマンドが干渉しない。git で管理できるので、チームで共有するときもそのまま使える。\nコマンドファイルの書き方 # 基本：ファイル名がコマンド名になる # .claude/commands/ 配下に置いた .md ファイルが1つのコマンドに対応する。\n.claude/ └── commands/ ├── preview.md → /preview ├── publish.md → /publish └── new-article.md → /new-article ファイルの中身は Claude への指示（プロンプト）をそのまま書く。\nローカルプレビューを更新してください。 ## 手順 1. `git status` で `files/` 配下の変更ファイルを確認する 2. 変更がなければ「コミットすべき変更がありません」と伝えて終了 3. `git add files/` を実行する 4. コミットメッセージを自動生成して `git commit` を実行する 5. 完了後にプレビューURL を案内する 手順を箇条書きにしておくと、Claude が指示通りに動く。\n引数を受け取る：$ARGUMENTS # $ARGUMENTS をプロンプト内に書くと、コマンド実行時にユーザが入力した文字列に置き換わる。\n引数で指定されたファイルをブラッシュアップしてください。 対象ファイル: $ARGUMENTS ## 手順 1. 対象ファイルを読み込む 2. 他の記事と比較してフロントマター・文体・構成を統一する 3. 変更内容を説明してから上書き保存する /new-article files/my-post.md と入力すると、$ARGUMENTS が files/my-post.md に置き換わってプロンプトが実行される。引数を省略した場合は $ARGUMENTS が空文字になり処理できないため、以下のようにプロンプト内でメッセージを出力して終了するよう指示しておく。\n対象ファイル: $ARGUMENTS $ARGUMENTS が空の場合は「対象ファイルを引数で指定してください（例: /new-article files/my-post.md）」と伝えて終了すること。 呼び出し方 # Claude Code のプロンプト入力欄で / を入力すると、利用可能なコマンドが補完候補として表示される。\n/preview /publish /new-article files/my-post.md 引数が必要なコマンドは、コマンド名の後ろにスペースを入れて続けて書く。\n/commands でコマンド一覧を確認する # CLAUDE.md に「起動時にコマンド一覧を表示すること」と書く方法も試したが、毎回確実に表示されるわけではなく安定しなかった。\nそこで、コマンド一覧を表示する専用コマンド /commands を作ることにした。.claude/commands/commands.md を以下の内容で作成する。\nこのプロジェクトで使えるカスタムコマンドの一覧を表示してください。 ​``` 利用可能なコマンド: /new-article \u0026lt;files/ファイル名\u0026gt; — 指定記事をブラッシュアップ /preview — ローカルプレビューを更新 /publish — 本番サーバへ同期 /commands — このヘルプを表示 ​``` 必要なときに /commands と入力するだけでヘルプが出る。起動時の自動表示にこだわるより、呼び出せるほうが確実だ。\n実際の使い方 # このブログでは以下の3コマンドを使っている。\n/new-article — 記事のブラッシュアップ # /new-article files/my-new-post.md 指定ファイルと既存記事を読み比べ、フロントマター・文体・構成を統一する。たたきの記事を書いたらまずこれを実行する。\n/preview — ローカルプレビュー更新 # /preview files/ 配下の変更ファイルを自動検出し、コミットメッセージを生成して git commit まで実行する。コミットをトリガーにした自動デプロイと組み合わせることで、コマンド一発でプレビューが更新される。\n/publish — 本番反映 # /publish 未プッシュのコミット一覧を確認してから git push を実行する。push をトリガーにしたビルド・同期フックと組み合わせることで、コマンド一発で本番まで反映される。\nまとめ # やること 方法 プロジェクト用コマンドを作る .claude/commands/\u0026lt;名前\u0026gt;.md にプロンプトを書く 全プロジェクト共通コマンドを作る ~/.claude/commands/\u0026lt;名前\u0026gt;.md に置く 引数を受け取る プロンプト内で $ARGUMENTS を使う コマンドを呼び出す / 入力で補完候補を表示、または /コマンド名 を直接入力 コマンド一覧をいつでも確認する /commands コマンドを作って呼び出す 「記事を書くことだけに集中したい」という動機から始めたが、仕組み自体はどんなプロジェクトにも応用できる。繰り返し使う操作をコマンド化しておくと、Claude Code がより使いやすくなる。\n","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/posts/claudecodecommand.ja/","section":"Posts","summary":"このブログの記事作成フローは、以前の記事で紹介したように git commit・git push をトリガーにプレビューや本番反映が自動で流れる仕組みになっている。\nただ、毎回 Claude Code を開いて「プレビューして」「本番に反映して」と伝えるのは手間だ。記事を書くことだけに集中したい。 そこで Claude Code のカスタムスラッシュコマンドを使って、これらの操作をワンコマンド化した。\nカスタムスラッシュコマンドとは # Claude Code には、独自のスラッシュコマンドを作れる仕組みがある。所定の場所に Markdown ファイルを置くだけで、そのファイル名がそのままコマンド名になる。\nファイル名 = コマンド名 というシンプルなルールだ。\nたとえば preview.md というファイルを置けば /preview というコマンドが使えるようになり、そのファイルの中身がプロンプトとして Claude に渡される。コマンドを入力するだけで、ファイルに書いた指示を Claude が実行してくれる。\n配置場所とスコープ # コマンドファイルの置き場所によって有効範囲が変わる。\n配置場所 スコープ \u003cプロジェクトルート\u003e/.claude/commands/ そのプロジェクトのみ ~/.claude/commands/ 全プロジェクトで有効 プロジェクト固有の操作はプロジェクト側に置く。こうすると他のプロジェクトで Claude を使っても同名のコマンドが干渉しない。git で管理できるので、チームで共有するときもそのまま使える。\n","title":"Claude Code のカスタムスラッシュコマンドでワークフローを自動化する","type":"posts"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/git/","section":"Tags","summary":"","title":"Git","type":"tags"},{"content":"このブログは Hugo で構築している。記事作成時は Ansible でローカルの Hugo を更新してプレビューし、確認後に本番へアップロードするフローで運用している。\nこれまではコミット後に ansible-playbook コマンドを手動で実行していたが、コミットしたら自動で Hugo が更新されプレビューに集中できる環境を作りたかった。\nこの記事では、git の post-commit hook と Ansible を組み合わせた自動デプロイの仕組みを紹介する。\n構成の概要 # このブログは以下の構成で運用している。\n役割 内容 記事管理 git リポジトリの files/*.md ローカルデプロイ Ansible Playbook (ansible_connection=local) プレビュー ローカルの Hugo サーバ 自動化の対象は「コミット → Ansible 実行」の部分だ。\nなぜ git hook を選んだか # git hook にした理由は以下の通りだ。\nコミット単位で管理できる — いつ・何を反映したか git 履歴に残る ロールバックが楽 — git revert すれば記事を戻せる 余計なプロセスを常駐させなくていい — デーモンが不要 実装 # フックの配置 # git のデフォルトフックディレクトリは .git/hooks/ だが、ここは git 管理外なので GitHub に push されない。別 PC でクローンしたときにフックが消えてしまう。\nそこで .githooks/ というディレクトリをリポジトリのルートに作り、git にフックの参照先を変更させる。\nmkdir -p .githooks git config core.hooksPath .githooks この設定で git は .git/hooks/ の代わりに .githooks/ を参照するようになる。.githooks/ はリポジトリに含まれるため、GitHub にも push できる。\npost-commit スクリプト # .githooks/post-commit に以下を配置する。\n#!/bin/bash # files/*.md の変更のみを対象にする CHANGED=$(git diff HEAD~1 --name-only 2\u0026gt;/dev/null | grep -E \u0026#39;^files/.*\\.md$\u0026#39;) [ -z \u0026#34;$CHANGED\u0026#34; ] \u0026amp;\u0026amp; exit 0 echo \u0026#34;========================================\u0026#34; echo \u0026#34; files/*.md の変更を検知しました\u0026#34; echo \u0026#34;$CHANGED\u0026#34; | sed \u0026#39;s/^/ /\u0026#39; echo \u0026#34;========================================\u0026#34; echo \u0026#34; Ansible を実行します...\u0026#34; cd \u0026#34;$(git rev-parse --show-toplevel)\u0026#34; ansible-playbook -i inventory.ini play.yml if [ $? -eq 0 ]; then echo \u0026#34;========================================\u0026#34; echo \u0026#34; デプロイ完了\u0026#34; echo \u0026#34; プレビュー : ローカルで確認してください\u0026#34; echo \u0026#34;========================================\u0026#34; else echo \u0026#34;[ERROR] Ansible が失敗しました\u0026#34; exit 1 fi 実行権限を付与してコミットする。\nchmod +x .githooks/post-commit git add .githooks/post-commit git commit -m \u0026#34;add: post-commit hook\u0026#34; ポイント：files/*.md だけを対象にする # 記事は files/*.md に配置するため、そのファイルが変更されたときだけ Ansible でデプロイするようにした。\n日常の使い方 # # 記事を書く vim files/新記事.md # コミットするだけで自動デプロイ git add files/新記事.md git commit -m \u0026#34;add: 新記事\u0026#34; # → Ansible が自動実行される # ローカルでプレビュー確認 コンテンツを以前の状態に戻す # 記事の内容を過去の状態に戻したい場合は、git の履歴を使って対応できる。\n特定ファイルを以前のバージョンに戻す # # 変更履歴を確認して戻したいコミットのハッシュを調べる git log --oneline files/記事名.md # 指定コミット時点の内容に戻す git checkout \u0026lt;コミットハッシュ\u0026gt; -- files/記事名.md # コミットすると hook が発火して自動デプロイされる git add files/記事名.md git commit -m \u0026#34;revert: 記事名を以前の状態に戻す\u0026#34; 直前のコミットを丸ごと取り消す # # 直前のコミットを打ち消す新しいコミットを作成する git revert HEAD # こちらも files/*.md が含まれていれば hook が発火して自動デプロイされる git revert は履歴を書き換えずに「取り消しコミット」を積む方法のため、安全に使える。\n別 PC でクローンした場合 # core.hooksPath はローカルの git 設定（.git/config）に書き込まれるため、クローンしても引き継がれない。新しい環境では以下を一度実行する必要がある。\ngit clone \u0026lt;リポジトリURL\u0026gt; cd \u0026lt;リポジトリ名\u0026gt; git config core.hooksPath .githooks まとめ # やること コマンド フック参照先の設定 git config core.hooksPath .githooks フックの有効化確認 git config --get core.hooksPath 手動でデプロイ ansible-playbook -i inventory.ini play.yml 「コミットしてブラウザを見るだけ」という状態になると、記事を書くことだけに集中できる。シンプルだが、実運用にちょうどいい自動化だと思っている。\n","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/posts/git-hook-ansible-auto-deploy.ja/","section":"Posts","summary":"このブログは Hugo で構築している。記事作成時は Ansible でローカルの Hugo を更新してプレビューし、確認後に本番へアップロードするフローで運用している。\nこれまではコミット後に ansible-playbook コマンドを手動で実行していたが、コミットしたら自動で Hugo が更新されプレビューに集中できる環境を作りたかった。\nこの記事では、git の post-commit hook と Ansible を組み合わせた自動デプロイの仕組みを紹介する。\n構成の概要 # このブログは以下の構成で運用している。\n役割 内容 記事管理 git リポジトリの files/*.md ローカルデプロイ Ansible Playbook (ansible_connection=local) プレビュー ローカルの Hugo サーバ 自動化の対象は「コミット → Ansible 実行」の部分だ。\nなぜ git hook を選んだか # git hook にした理由は以下の通りだ。\n","title":"git commit で Hugo ローカルサーバへの自動デプロイを実現する","type":"posts"},{"content":"前回の記事では git commit をトリガーに Ansible でローカルの Hugo を自動更新する仕組みを構築した。今回はその続きで、git push のタイミングで本番サーバへの同期も自動化した話を紹介する。\nやりたいこと # これまでは本番反映を手動で実行していたが、git push に統合し、push するだけで本番まで反映されるようにしたかった。\ngit に post-push フックはない # git には post-commit のように「push 後」に処理を実行する post-push フックが存在しない。\n代わりに pre-push フックを使う。これは push が実行される直前に呼ばれるフックで、処理が失敗（exit 1）すると push 自体を中止できる。\n本番同期に失敗したまま GitHub へ push されるのを防げるため、むしろ都合がいい。\n実装 # .githooks/pre-push に本番同期の処理を記述し、実行権限を付与してコミットする。\nchmod +x .githooks/pre-push git add .githooks/pre-push git commit -m \u0026#34;add: pre-push hook for production sync\u0026#34; 完成したフロー # git commit（files/*.md を含む） → post-commit hook → Ansible → ローカルプレビュー確認 git push → pre-push hook → Hugo ビルド → rsync → 本番同期 → GitHub へ push コミットしてプレビューを確認し、問題なければ push するだけで本番まで反映される。\n本番を以前の状態に戻す # 誤った内容を push してしまった場合は、git revert で取り消しコミットを作って再度 push する。pre-push hook が発火するため、自動的に本番にも反映される。\n# 直前の push を取り消す git revert HEAD git push 特定のコミットまで戻したい場合は履歴を確認して対象を指定する。\n# 履歴を確認 git log --oneline # 指定コミットを取り消す git revert \u0026lt;コミットハッシュ\u0026gt; git push git revert は履歴を書き換えずに「取り消しコミット」を積む方法のため、安全に使える。\n特定ファイルだけ戻す # git revert はコミット単位の操作のため、1ファイルだけ戻すには使えない。その場合は git checkout で過去のバージョンを取り出す。\n# 対象ファイルの変更履歴を確認 git log --oneline files/記事名.md # 指定コミット時点の内容に戻す git checkout \u0026lt;コミットハッシュ\u0026gt; -- files/記事名.md # コミットして push → pre-push hook が発火して本番に反映 git add files/記事名.md git commit -m \u0026#34;revert: 記事名を以前の状態に戻す\u0026#34; git push 別 PC でクローンした場合 # core.hooksPath の設定はローカルにしか保存されないため、クローン後に一度実行が必要だ。\ngit clone \u0026lt;リポジトリURL\u0026gt; cd \u0026lt;リポジトリ名\u0026gt; git config core.hooksPath .githooks まとめ # やること タイミング ローカル Hugo へ自動デプロイ git commit（post-commit hook） 本番サーバへ自動同期 git push（pre-push hook） ","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/posts/git-push-production-sync.ja/","section":"Posts","summary":"前回の記事では git commit をトリガーに Ansible でローカルの Hugo を自動更新する仕組みを構築した。今回はその続きで、git push のタイミングで本番サーバへの同期も自動化した話を紹介する。\nやりたいこと # これまでは本番反映を手動で実行していたが、git push に統合し、push するだけで本番まで反映されるようにしたかった。\ngit に post-push フックはない # git には post-commit のように「push 後」に処理を実行する post-push フックが存在しない。\n代わりに pre-push フックを使う。これは push が実行される直前に呼ばれるフックで、処理が失敗（exit 1）すると push 自体を中止できる。\n本番同期に失敗したまま GitHub へ push されるのを防げるため、むしろ都合がいい。\n実装 # .githooks/pre-push に本番同期の処理を記述し、実行権限を付与してコミットする。\n","title":"git push で本番サーバへの自動同期を実現する","type":"posts"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/","section":"Home","summary":"","title":"Home","type":"page"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/hugo/","section":"Tags","summary":"","title":"Hugo","type":"tags"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/lets-encrypt/","section":"Tags","summary":"","title":"Let's Encrypt","type":"tags"},{"content":"HTTPS 対応サイトを運用していると、「証明書が増えてきた」「更新テストをしたい」「自動更新の設定が合っているか不安」といった場面が定期的にやってくる。\nこの記事では、Certbot の日常運用でよく使う操作を一か所にまとめた。セットアップ手順ではなく、運用フェーズで実際に使うコマンドと設定に絞っている。\n1. 証明書の確認・削除 # まず現状把握から。取得済みの証明書は一覧で確認できる。\nsudo certbot certificates 有効期限・ドメイン・証明書名が表示されるので、不要なものがあれば削除する。\n# 対話形式で選択 sudo certbot delete # 証明書名を直接指定 sudo certbot delete --cert-name example.com 削除すると /etc/letsencrypt/live/・archive/・renewal/ 配下のファイルが一括で消え、自動更新の対象からも外れる。\n2. dry-run で事前テスト # 証明書の取得・更新に失敗し続けると Let\u0026rsquo;s Encrypt のレート制限に引っかかる。本番実行の前に dry-run でテストしておくのが鉄則だ。\ndry-run ではステージング環境が使われるため、レート制限の心配なく何度でも試せる。\n新規取得のテスト # sudo certbot certonly --dry-run --webroot \\ -w /var/www/acme-challenge \\ -d example.com 既存証明書の更新テスト（推奨） # 既存の .conf 設定（authenticator = webroot など）を読み込んで実行される。\nsudo certbot renew --dry-run --cert-name example.com 注意： certbot certonly --dry-run は新規取得扱いのため .conf を参照しない。更新のテストには必ず certbot renew --dry-run を使うこと。\n3. Apache ポート 80 の設定（ACME challenge 専用） # webroot 認証を使う場合、ポート 80 を Let\u0026rsquo;s Encrypt の認証専用にして、それ以外のアクセスは HTTPS にリダイレクトする構成が扱いやすい。\n\u0026lt;VirtualHost *:80\u0026gt; ServerName example.com ServerAlias www.example.com blog.example.com DocumentRoot /var/www/acme-challenge \u0026lt;Directory /var/www/acme-challenge\u0026gt; Options -Indexes AllowOverride None Require all granted \u0026lt;/Directory\u0026gt; RewriteEngine On # ACME challenge はそのまま通す RewriteRule ^/\\.well-known/acme-challenge/ - [L] # それ以外は HTTPS にリダイレクト RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] ErrorLog ${APACHE_LOG_DIR}/acme-challenge-error.log CustomLog ${APACHE_LOG_DIR}/acme-challenge-access.log combined \u0026lt;/VirtualHost\u0026gt; 設定のポイント：\nServerAlias にサブドメインをまとめて記載する certbot の webroot-path と DocumentRoot を一致させる /.well-known/acme-challenge/ ディレクトリを事前に作成しておく sudo mkdir -p /var/www/acme-challenge/.well-known/acme-challenge sudo chown -R www-data:www-data /var/www/acme-challenge 4. 自動更新を crontab に設定 # まず systemd timer を確認する（Ubuntu/Debian） # certbot をパッケージインストールした場合、certbot.timer が既に動いていることが多い。二重実行を避けるため、先に確認する。\nsystemctl status certbot.timer timer が有効（active (waiting)）であれば、crontab の追加設定は不要だ。\ncrontab に登録する場合 # timer が無効だった場合や、明示的に cron で管理したい場合は以下を登録する。\nsudo crontab -e # Let\u0026#39;s Encrypt 証明書の自動更新（1日1回） # 実際に更新されたときだけ Apache をリロード 30 3 * * * certbot renew --quiet --deploy-hook \u0026#34;systemctl reload apache2\u0026#34; \u0026gt;\u0026gt; /var/log/certbot-renew.log 2\u0026gt;\u0026amp;1 --deploy-hook を使うと、証明書が実際に更新されたときだけ Apache がリロードされる。\n5. 設定変更後の必須コマンド # VirtualHost の設定を変更したら、必ず構文チェックを挟んでからリロードする。\n# 構文チェック（エラーがあれば反映前に気づける） sudo apachectl configtest # 問題なければ Apache リロード sudo systemctl reload apache2 configtest で Syntax OK が出ることを確認してから reload するクセをつけておくと、ミスによるサービス停止を防げる。\nまとめ # やりたいこと コマンド 証明書一覧を確認 certbot certificates 証明書を削除 certbot delete --cert-name \u0026lt;name\u0026gt; 更新のテスト certbot renew --dry-run --cert-name \u0026lt;name\u0026gt; 自動更新の確認 systemctl status certbot.timer Apache 構文チェック apachectl configtest Certbot は一度設定してしまうと普段あまり触らないが、いざというときにコマンドを探し回るのは避けたい。このチートシートが手元にあれば、運用時の迷いを減らせるはずだ。\n","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/posts/certbot-cheatsheet.ja/","section":"Posts","summary":"HTTPS 対応サイトを運用していると、「証明書が増えてきた」「更新テストをしたい」「自動更新の設定が合っているか不安」といった場面が定期的にやってくる。\nこの記事では、Certbot の日常運用でよく使う操作を一か所にまとめた。セットアップ手順ではなく、運用フェーズで実際に使うコマンドと設定に絞っている。\n1. 証明書の確認・削除 # まず現状把握から。取得済みの証明書は一覧で確認できる。\nsudo certbot certificates 有効期限・ドメイン・証明書名が表示されるので、不要なものがあれば削除する。\n# 対話形式で選択 sudo certbot delete # 証明書名を直接指定 sudo certbot delete --cert-name example.com 削除すると /etc/letsencrypt/live/・archive/・renewal/ 配下のファイルが一括で消え、自動更新の対象からも外れる。\n2. dry-run で事前テスト # 証明書の取得・更新に失敗し続けると Let’s Encrypt のレート制限に引っかかる。本番実行の前に dry-run でテストしておくのが鉄則だ。\ndry-run ではステージング環境が使われるため、レート制限の心配なく何度でも試せる。\n新規取得のテスト # sudo certbot certonly --dry-run --webroot \\ -w /var/www/acme-challenge \\ -d example.com 既存証明書の更新テスト（推奨） # 既存の .conf 設定（authenticator = webroot など）を読み込んで実行される。\n","title":"Let's Encrypt / Certbot 運用チートシート","type":"posts"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/rsync/","section":"Tags","summary":"","title":"Rsync","type":"tags"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/ssl/","section":"Tags","summary":"","title":"SSL","type":"tags"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/categories/%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9/","section":"Categories","summary":"","title":"インフラ","type":"categories"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/%E8%87%AA%E5%8B%95%E5%8C%96/","section":"Tags","summary":"","title":"自動化","type":"tags"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/categories/%E9%96%8B%E7%99%BA%E3%83%84%E3%83%BC%E3%83%AB/","section":"Categories","summary":"","title":"開発ツール","type":"categories"},{"content":"","date":"May 5, 2026","externalUrl":null,"permalink":"/blog/tags/%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83/","section":"Tags","summary":"","title":"開発環境","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/2048/","section":"Tags","summary":"","title":"2048","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/categories/ai-tools/","section":"Categories","summary":"","title":"AI Tools","type":"categories"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/apt/","section":"Tags","summary":"","title":"Apt","type":"tags"},{"content":"sudo apt update を実行すると、次のような警告が表示されることがあります。\nW: Failed to fetch https://ppa.launchpadcontent.net/gns3/ppa/ubuntu/dists/... connection timed out 警告の原因 # 追加済みの PPA（Personal Package Archive）リポジトリへの接続がタイムアウトしたことを示しています。\nW: プレフィックスは Warning（警告） であり、エラー（E:）ではありません。パッケージのインストールや更新など、通常の apt 操作への影響は軽微です。ただし、apt update のたびに表示されるため、放置すると煩わしくなります。\nタイムアウトが発生する主な原因は次の2つです。\nLaunchpad サーバー側の一時的な障害・メンテナンス PPA を追加したままにしており、サーバーへの疎通が取れなくなっている 対処法 # 一時的な問題の場合 # 時間をおいてから再度 sudo apt update を実行してください。サーバー側の問題であれば自然に解消されます。\nPPA を削除する（根本解決） # 該当ソフトウェアを今後使う予定がない場合は、PPA ごと削除するのが根本的な解決策です。\nsudo add-apt-repository --remove ppa:gns3/ppa sudo apt update add-apt-repository --remove を実行すると /etc/apt/sources.list.d/ 内の該当エントリが削除され、以降の apt update では警告が表示されなくなります。\n登録されている PPA を確認する # どの PPA が登録されているかを確認したい場合は、以下のコマンドを使います。\ngrep -r \u0026#34;^deb\u0026#34; /etc/apt/sources.list /etc/apt/sources.list.d/ 不要な PPA を見つけたら同様に add-apt-repository --remove で削除できます。\nまとめ # 状況 対処 一時的なネットワーク障害 時間をおいて再実行 PPA が不要 add-apt-repository --remove ppa:XXX/YYY で削除 不要な PPA は都度削除しておくと、apt update をクリーンな状態で実行できます。\n","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/posts/aptupdatewarn.ja/","section":"Posts","summary":"sudo apt update を実行すると、次のような警告が表示されることがあります。\nW: Failed to fetch https://ppa.launchpadcontent.net/gns3/ppa/ubuntu/dists/... connection timed out 警告の原因 # 追加済みの PPA（Personal Package Archive）リポジトリへの接続がタイムアウトしたことを示しています。\nW: プレフィックスは Warning（警告） であり、エラー（E:）ではありません。パッケージのインストールや更新など、通常の apt 操作への影響は軽微です。ただし、apt update のたびに表示されるため、放置すると煩わしくなります。\nタイムアウトが発生する主な原因は次の2つです。\nLaunchpad サーバー側の一時的な障害・メンテナンス PPA を追加したままにしており、サーバーへの疎通が取れなくなっている 対処法 # 一時的な問題の場合 # 時間をおいてから再度 sudo apt update を実行してください。サーバー側の問題であれば自然に解消されます。\n","title":"apt update の警告: PPA への接続タイムアウトが出たときの対処法","type":"posts"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/autoinstall/","section":"Tags","summary":"","title":"Autoinstall","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/cloud-init/","section":"Tags","summary":"","title":"Cloud-Init","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/categories/infrastructure/","section":"Categories","summary":"","title":"Infrastructure","type":"categories"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/kvm/","section":"Tags","summary":"","title":"KVM","type":"tags"},{"content":" はじめに # KVM 環境で Ubuntu 26.04 の VM を何度も作成する機会があり、毎回インストーラーを手動操作するのが手間だったため、完全ノータッチで VM を立ち上げる構成を整えた。\nautoinstall は Ubuntu のインストーラー（Subiquity）が提供する機能で、設定ファイルを seed ISO として渡すだけで、対話操作なしにインストールが自動で完了する。\nautoinstall の仕組み # Ubuntu 20.04 以降のサーバーインストーラー（Subiquity）は autoinstall という設定ファイル形式をサポートしている。#cloud-config ヘッダーを持つ YAML ファイルに、ロケール・ネットワーク・ストレージ・ユーザーなどを記述しておくと、インストーラーが対話なしに処理を進める。\nこの設定ファイル（user-data）をインストーラーに渡す方法はいくつかあるが、今回は NoCloud データソースを使った seed ISO 方式を採用した。\nseed ISO の中身は user-data と meta-data の 2 ファイルだけで、ボリュームラベルを cidata にするルールがある。\nセットアップ手順 # 1. パスワードハッシュ生成 # autoinstall の identity.password には平文ではなく SHA-512 ハッシュを渡す必要がある。openssl passwd -6 で生成する。\nPASSWORD_HASH=$(openssl passwd -6 \u0026#34;${PASSWORD}\u0026#34;) 2. user-data の作成 # #cloud-config autoinstall: version: 1 locale: ja_JP.UTF-8 keyboard: layout: jp network: version: 2 ethernets: enp1s0: addresses: - 192.168.122.43/24 gateway4: 192.168.122.1 nameservers: addresses: - 8.8.8.8 - 8.8.4.4 storage: layout: name: lvm identity: hostname: ubuntu26 username: ubuntu password: \u0026#34;$6$...\u0026#34; # SHA-512 ハッシュ ssh: install-server: true allow-pw: true packages: - vim - curl - git late-commands: - echo \u0026#34;ubuntu ALL=(ALL) NOPASSWD:ALL\u0026#34; \u0026gt; /target/etc/sudoers.d/ubuntu late-commands はインストール完了直後にインストーラー環境から実行されるコマンドで、/target がインストール先ファイルシステムのマウントポイントになる。ここで sudo の NOPASSWD 設定を投入している。\n3. seed ISO 作成 # xorriso で user-data と meta-data を焼いて seed ISO を作成する。ボリュームラベル cidata が cloud-init の NoCloud データソース認識に必要。\nxorriso -as mkisofs \\ -volid cidata \\ -joliet -rock \\ -output seed.iso \\ user-data \\ meta-data 4. ディスクイメージ作成 # qemu-img create -f qcow2 ubuntu26.qcow2 20G qcow2 形式の thin provisioning のため、実際の使用量に応じて拡張される。\n5. virt-install 実行 # virt-install \\ --name ubuntu26 \\ --vcpus 2 \\ --memory 4096 \\ --disk \u0026#34;path=ubuntu26.qcow2,format=qcow2\u0026#34; \\ --location \u0026#34;ubuntu-26.04-live-server-amd64.iso,kernel=casper/vmlinuz,initrd=casper/initrd\u0026#34; \\ --disk \u0026#34;path=seed.iso,device=cdrom\u0026#34; \\ --os-variant \u0026#34;ubuntu24.04\u0026#34; \\ --network network=default \\ --graphics none \\ --console pty,target_type=serial \\ --extra-args \u0026#34;console=ttyS0,115200n8 autoinstall ds=nocloud\u0026#34; \\ --noautoconsole \\ --wait -1 --extra-args に autoinstall ds=nocloud を渡すことで、インストーラーが seed ISO から user-data を読み込む。--wait -1 でインストール完了まで待機する。--os-variant は Ubuntu 26.04 のエントリが libosinfo 未収録のため ubuntu24.04 を代用している。\nまとめ # 項目 内容 インストール方式 autoinstall（Subiquity） 設定の渡し方 seed ISO（NoCloud データソース） seed ISO 作成ツール xorriso ネットワーク netplan 静的IP ","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/posts/kvm-ubuntu2604-autoinstall-virsh.ja/","section":"Posts","summary":"はじめに # KVM 環境で Ubuntu 26.04 の VM を何度も作成する機会があり、毎回インストーラーを手動操作するのが手間だったため、完全ノータッチで VM を立ち上げる構成を整えた。\nautoinstall は Ubuntu のインストーラー（Subiquity）が提供する機能で、設定ファイルを seed ISO として渡すだけで、対話操作なしにインストールが自動で完了する。\nautoinstall の仕組み # Ubuntu 20.04 以降のサーバーインストーラー（Subiquity）は autoinstall という設定ファイル形式をサポートしている。#cloud-config ヘッダーを持つ YAML ファイルに、ロケール・ネットワーク・ストレージ・ユーザーなどを記述しておくと、インストーラーが対話なしに処理を進める。\nこの設定ファイル（user-data）をインストーラーに渡す方法はいくつかあるが、今回は NoCloud データソースを使った seed ISO 方式を採用した。\nseed ISO の中身は user-data と meta-data の 2 ファイルだけで、ボリュームラベルを cidata にするルールがある。\n","title":"KVM/virsh で Ubuntu 26.04 を自動セットアップ — autoinstall + seed ISO","type":"posts"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/ppa/","section":"Tags","summary":"","title":"PPA","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/qwen-code/","section":"Tags","summary":"","title":"Qwen Code","type":"tags"},{"content":" はじめに # Alibaba が開発した AI コーディングエージェント Qwen Code は、OpenAI 互換 API を持つ任意のエンドポイントを向け先として指定できる。今回は さくらのAI Engine（api.ai.sakura.ad.jp）を API バックエンドとして設定し、KVM 上の Ubuntu 26.04 VM で動作させた。\n通信制御に Squid 明示プロキシ（FQDN ホワイトリスト方式） を導入した。プロキシのアクセスログを確認したところ、さくらの API 以外にも Alibaba Cloud の外部サービスへのアクセスが発生していることが判明した。\n環境構成 # iptables（ファイアウォール） # 外向き通信は デフォルト DROP とし、Squid 実行ユーザのプロセスのみ 443/80 への通信を許可する構成にした。\n一般ユーザ → port 443 直接 → [iptables DROP] 一般ユーザ → Squid :3128 → [iptables ACCEPT（Squid実行ユーザ）] → インターネット これにより、プロキシを経由しない通信は物理的に不可能になる。\nSquid 明示プロキシ（SSL Bump） # Squid を SSL Bump モードで動作させ、HTTPS 通信の FQDN を検査したうえでホワイトリスト判定する。証明書の内容は復号するが、外部に転送するのはホワイトリストに一致したものだけ。\nFQDN ホワイトリスト\nFQDN 用途 .ubuntu.com apt（全サブドメイン） .canonical.com Canonical CDN api.ai.sakura.ad.jp さくらのAI Engine API deb.nodesource.com Node.js apt リポジトリ registry.npmjs.org npm パッケージレジストリ Squid の設定（抜粋）：\nhttp_port 3128 ssl-bump \\ cert=/etc/squid/ssl_cert/squid-ca.crt \\ key=/etc/squid/ssl_cert/squid-ca.key \\ generate-host-certificates=on acl whitelist dstdomain \u0026#34;/etc/squid/whitelist.txt\u0026#34; ssl_bump peek step1 ssl_bump bump all http_access allow CONNECT http_access allow whitelist http_access deny all Qwen Code の設定（settings.json） # さくらのAI Engine を OpenAI 互換プロバイダとして登録し、プロキシ設定も settings.json に埋め込む。\n{ \u0026#34;modelProviders\u0026#34;: { \u0026#34;openai\u0026#34;: [{ \u0026#34;id\u0026#34;: \u0026#34;Qwen3-Coder-480B-A35B-Instruct-FP8\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;さくらのAI Engine\u0026#34;, \u0026#34;baseUrl\u0026#34;: \u0026#34;https://api.ai.sakura.ad.jp/v1/chat/completions\u0026#34; }] }, \u0026#34;env\u0026#34;: { \u0026#34;HTTPS_PROXY\u0026#34;: \u0026#34;http://127.0.0.1:3128\u0026#34;, \u0026#34;NODE_EXTRA_CA_CERTS\u0026#34;: \u0026#34;/etc/squid/ssl_cert/squid-ca.crt\u0026#34; }, \u0026#34;proxy\u0026#34;: \u0026#34;http://127.0.0.1:3128\u0026#34; } Qwen Code の実行結果 # 以下は Qwen Code を実行した際のセッション終了サマリー。\n╭──────────────────────────────────────────────────────────────────────╮ │ │ │ エージェントを終了します。さようなら! │ │ │ │ インタラクション概要 │ │ セッションID: 6a4e7ba5-566a-4159-a106-d7747a488ace │ │ ツール呼び出し: 10 ( ✓ 10 x 0 ) │ │ 成功率: 100.0% │ │ ユーザー同意: 100.0% (2 レビュー済み) │ │ コード変更: +50 -0 │ │ │ │ パフォーマンス │ │ 経過時間: 2m 18s │ │ エージェント稼働時間: 1m 26s │ │ » API時間: 37.5s (43.1%) │ │ » ツール時間: 49.5s (56.9%) │ │ │ │ モデル使用量 リクエスト 入力トークン 出力トークン │ │ ───────────────────────────────────────────────────────── │ │ Qwen3-Coder-480B-A35B-Instruct-FP8 12 232,846 1,018 │ │ │ ╰──────────────────────────────────────────────────────────────────────╯ ツール呼び出し 10 回すべて成功し、2分18秒のセッションで 50 行のコードを生成した。API へのリクエストは 12 回、入力トークンは 23 万超となった。\nプロキシで発見した「想定外のアクセス」 # Squid のアクセスログを確認すると、さくらのAI Engine 以外に 2 種類の接続先が確認できた。\n# npmjs.org（パッケージインストール時） TCP_MISS/200 GET https://registry.npmjs.org/@qwen-code%2Fqwen-code # さくらのAI Engine（意図したアクセス） TCP_MISS/200 POST https://api.ai.sakura.ad.jp/v1/chat/completions # ← 問題のアクセス（11回試行、全件 403 でブロック） TCP_DENIED/403 POST https://gb4w8c3ygj-default-sea.rum.aliyuncs.com/ gb4w8c3ygj-default-sea.rum.aliyuncs.com とは何か # ドメインを分解すると正体が分かる。\n部分 意味 aliyuncs.com Alibaba Cloud（アリクラウド）の公式ドメイン rum Real User Monitoring — ユーザー行動・エラーを収集するサービス sea Southeast Asia リージョン gb4w8c3ygj Alibaba Cloud ARMS 上の qwen-code 固有プロジェクト ID これは Alibaba Cloud ARMS（Application Real-Time Monitoring Service）の RUM エンドポイントだ。Qwen Code は起動・使用のたびにこのエンドポイントへ POST を送信し、以下のようなテレメトリーデータを収集しようとする。\n操作イベント（どの機能・コマンドを使ったか） エラー・例外情報 パフォーマンス指標（起動時間、レスポンス時間） OS・ランタイム環境情報 セッションの開始・終了タイミング Qwen Code は Alibaba グループが開発したツールであり、使用データが同グループのクラウド監視基盤（中国企業のサーバー）に送信される設計になっている。\nソースコードでの確認 # このアクセスは qwen-code のソースコード（packages/core/src/telemetry/qwen-logger/qwen-logger.ts）にハードコードされている。\n// Usage statistics collection endpoint const USAGE_STATS_HOSTNAME = \u0026#39;gb4w8c3ygj-default-sea.rum.aliyuncs.com\u0026#39;; const USAGE_STATS_PATH = \u0026#39;/\u0026#39;; const RUN_APP_ID = \u0026#39;gb4w8c3ygj@851d5d500f08f92\u0026#39;; コメントに // Usage statistics collection endpoint と明記されており、意図的に組み込まれた使用統計収集機構であることが確認できる。\n公式ドキュメントへの記載 # 公式の Terms of Service / Privacy Notice には「匿名の使用統計とテレメトリーデータを収集することがある」と記載されているが、aliyuncs.com への接続や RUM サービスの利用についての記載、並びに、ユーザーが設定しなくても自動的に接続試行されることへの言及も、私には見つけられなかった。\nテレメトリー除去フォークの存在 # 同様の問題を認識した開発者が qwen-code-no-telemetry というフォークを公開している。このフォークは以下の対応を行っている。\n対象 対応内容 packages/core/src/telemetry/ すべて no-op 実装に置き換え @opentelemetry/* パッケージ 全削除 インストールID生成 常に 00000000-0000-0000-0000-000000000000 を返す固定値に変更 今回 Squid がブロックした gb4w8c3ygj-default-sea.rum.aliyuncs.com への接続は、まさにこのフォークが封じようとしているものと完全に一致している。\nSquid がブロックしていることの確認 # 今回の接続では、11 回の試行があった。\n特に通信制限がなければ、何らかのデータが Alibaba に送信されるようだ。\nまとめ # 確認事項 結果 さくらのAI Engine を API として使用 成功 Qwen Code の動作（ツール呼び出し・コード生成） 正常 Squid による通信の可視化 FQDN→URI 単位で全アクセスを記録 aliyuncs.com へのテレメトリー送信 Squid が全件ブロック（403） Qwen Code は、さくらのAI Engine への API アクセスとは別に、Alibaba Cloud のテレメトリーサービス（RUM）への接続がソースコードに含まれており、意図せず通信が試みられる可能性があるようだ。\n新しいソフトウェアを実行する際は、今回のように通信先をプロキシで可視化し、不要・不明な通信は DENY する構成を取ることが大切だと感じた。テレメトリーを許容しない場合の選択肢として、qwen-code-no-telemetry フォークの存在も参考になるかもしれない。\n","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/posts/qwen-code-sakura-squid-telemetry.ja/","section":"Posts","summary":"はじめに # Alibaba が開発した AI コーディングエージェント Qwen Code は、OpenAI 互換 API を持つ任意のエンドポイントを向け先として指定できる。今回は さくらのAI Engine（api.ai.sakura.ad.jp）を API バックエンドとして設定し、KVM 上の Ubuntu 26.04 VM で動作させた。\n通信制御に Squid 明示プロキシ（FQDN ホワイトリスト方式） を導入した。プロキシのアクセスログを確認したところ、さくらの API 以外にも Alibaba Cloud の外部サービスへのアクセスが発生していることが判明した。\n環境構成 # iptables（ファイアウォール） # 外向き通信は デフォルト DROP とし、Squid 実行ユーザのプロセスのみ 443/80 への通信を許可する構成にした。\n","title":"Qwen Code — Squidプロキシで見えた「隠れたアクセス」","type":"posts"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/categories/security/","section":"Categories","summary":"","title":"Security","type":"categories"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/squid/","section":"Tags","summary":"","title":"Squid","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/ubuntu/","section":"Tags","summary":"","title":"Ubuntu","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/virsh/","section":"Tags","summary":"","title":"Virsh","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/%E3%82%B2%E3%83%BC%E3%83%A0/","section":"Tags","summary":"","title":"ゲーム","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/%E3%81%95%E3%81%8F%E3%82%89%E3%81%AEai/","section":"Tags","summary":"","title":"さくらのAI","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3/","section":"Tags","summary":"","title":"セキュリティ","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/categories/%E3%81%9D%E3%81%AE%E4%BB%96/","section":"Categories","summary":"","title":"その他","type":"categories"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/%E3%83%86%E3%83%AC%E3%83%A1%E3%83%88%E3%83%AA%E3%83%BC/","section":"Tags","summary":"","title":"テレメトリー","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/%E3%83%97%E3%83%AD%E3%82%AD%E3%82%B7/","section":"Tags","summary":"","title":"プロキシ","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E5%B4%A9%E3%81%97/","section":"Tags","summary":"","title":"ブロック崩し","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/%E4%BB%AE%E6%83%B3%E5%8C%96/","section":"Tags","summary":"","title":"仮想化","type":"tags"},{"content":"","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/tags/%E6%9A%87%E3%81%A4%E3%81%B6%E3%81%97/","section":"Tags","summary":"","title":"暇つぶし","type":"tags"},{"content":" 暇つぶしゲーム # このサイトに暇つぶし用のゲームページを置いた。\n2048 やブロック崩しなど、ちょっとした隙間時間に遊べるゲームを集めている。\n暇つぶしゲームはこちら\n","date":"May 4, 2026","externalUrl":null,"permalink":"/blog/posts/game-introduction.ja/","section":"Posts","summary":"暇つぶしゲーム # このサイトに暇つぶし用のゲームページを置いた。\n2048 やブロック崩しなど、ちょっとした隙間時間に遊べるゲームを集めている。\n暇つぶしゲームはこちら\n","title":"暇つぶしゲーム置いてみた","type":"posts"},{"content":"","date":"May 3, 2026","externalUrl":null,"permalink":"/blog/tags/dovecot/","section":"Tags","summary":"","title":"Dovecot","type":"tags"},{"content":"","date":"May 3, 2026","externalUrl":null,"permalink":"/blog/tags/oci/","section":"Tags","summary":"","title":"OCI","type":"tags"},{"content":"","date":"May 3, 2026","externalUrl":null,"permalink":"/blog/tags/postfix/","section":"Tags","summary":"","title":"Postfix","type":"tags"},{"content":"","date":"May 3, 2026","externalUrl":null,"permalink":"/blog/tags/roundcube/","section":"Tags","summary":"","title":"Roundcube","type":"tags"},{"content":"","date":"May 3, 2026","externalUrl":null,"permalink":"/blog/tags/spamassassin/","section":"Tags","summary":"","title":"SpamAssassin","type":"tags"},{"content":"","date":"May 3, 2026","externalUrl":null,"permalink":"/blog/tags/%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9/","section":"Tags","summary":"","title":"インフラ","type":"tags"},{"content":"","date":"May 3, 2026","externalUrl":null,"permalink":"/blog/categories/%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9%E6%A7%8B%E7%AF%89/","section":"Categories","summary":"","title":"インフラ構築","type":"categories"},{"content":"","date":"May 3, 2026","externalUrl":null,"permalink":"/blog/tags/%E3%83%88%E3%83%A9%E3%83%96%E3%83%AB%E3%82%B7%E3%83%A5%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0/","section":"Tags","summary":"","title":"トラブルシューティング","type":"tags"},{"content":"","date":"May 3, 2026","externalUrl":null,"permalink":"/blog/tags/%E3%83%A1%E3%83%BC%E3%83%AB%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC/","section":"Tags","summary":"","title":"メールサーバー","type":"tags"},{"content":"Ubuntu 24.04 / Postfix / Dovecot / Roundcube / SpamAssassin による受信専用メールサーバー\n1. 要件 # 機能要件 # 項目 内容 メールドメイン blackratel.com 受信 外部メールサーバーから SMTP port 25 で受信 メール閲覧 IMAP port 143（localhost のみ）/ Roundcube WebUI スパムフィルタ SpamAssassin（content_filter 方式） 送信 対象外（受信専用） 認証 Dovecot 認証（IMAP・Roundcube ログイン） メールボックス形式 Maildir ユーザー管理 バーチャルユーザー（OS ユーザーと分離） 非機能要件 # 項目 内容 OS Ubuntu 24.04 LTS TLS 証明書 Postfix（port 25 STARTTLS）と Apache（HTTPS）のみ使用。Dovecot は localhost のみのため SSL 不要 パスワード保存 ARGON2ID ハッシュ ホスティング OCI Free Tier（アウトバウンド port 25 はブロックのため送信不可） スコープ外 # メール送信（OCI Free Tier のためアウトバウンド port 25 が利用不可） SPF / DKIM / DMARC 設定（送信しないため不要） 2. 環境・構成情報 # ミドルウェア # ソフトウェア 役割 Postfix MTA（外部からの受信） Dovecot MDA + MRA（メール保存・IMAP 提供） SpamAssassin スパムフィルタ（content_filter 方式） Roundcube WebUI（ブラウザでのメール閲覧） Apache2 Roundcube のリバースプロキシ ポート構成 # ポート プロトコル 用途 接続元 25 SMTP 外部メールサーバーからの受信 外部（OCI Security List + UFW で開放） 143 IMAP Roundcube → Dovecot（メール取得） localhost のみ 443 HTTPS Roundcube WebUI 外部 port 110 / 465 / 587 / 993 / 995 は無効化。\nディレクトリ構成 # /etc/postfix/ ├── main.cf # メイン設定 ├── master.cf # デーモン・ポート設定 ├── virtual_mailbox_domains # 受け付けるドメイン一覧 └── virtual_mailbox_maps # メールアドレス → Maildir パスのマッピング /etc/dovecot/ ├── dovecot.conf # メイン（include のみ） ├── users # バーチャルユーザー管理ファイル └── conf.d/ ├── 10-auth.conf # 認証方式設定 ├── 10-mail.conf # Maildir パス設定・INBOX 自動作成 ├── 10-master.conf # LMTP / IMAP ポート定義 ├── 10-ssl.conf # SSL 無効（ssl = no、localhost IMAP のため不要） └── auth-passwdfile.conf.ext # passdb / userdb 定義 /var/mail/vmail/ # OS ユーザー vmail が所有 └── blackratel.com/ └── \u0026lt;user\u0026gt;/ # Maildir ├── cur/ ├── new/ └── tmp/ /var/www/blackratel.com/ └── xxxxxx/ # Roundcube インストール先 ├── config/config.inc.php └── temp/roundcube.db # SQLite データベース DNS 設定 # レコード 値 blackratel.com. MX 10 blackratel.com. MX の向き先を blackratel.com. にすることで、\nWeb で使用している Let\u0026rsquo;s Encrypt 証明書をそのまま共用する。\n3. コンポーネント間の連携 # Postfix\nport 25：外部メールサーバーからの受信 受信メールを content_filter = spamassassin で SpamAssassin に転送 SpamAssassin 処理後のメールを LMTP（Unix Socket）経由で Dovecot へ引き渡す pickup サービスに -o content_filter= を設定しループを防止 Dovecot\nport 143（localhost のみ・SSL なし）：Roundcube へ IMAP 提供 受け取ったメールを Maildir 形式で /var/mail/vmail/blackratel.com/ に保存 auth-worker が /etc/dovecot/users を参照し IMAP 認証を管理 INBOX が存在しない場合は初回ログイン時に自動作成 Postfix との auth 連携なし（受信専用のため SMTP AUTH 不要） Roundcube\nhttps://blackratel.com/xxxxxx/ で WebUI を提供 localhost:143 経由で Dovecot に IMAP 接続（メール閲覧のみ・送信不可） データベース：SQLite（/var/www/blackratel.com/xxxxxx/temp/roundcube.db） SpamAssassin\nspamd デーモンが port 783 でリッスン（localhost のみ） spamc pipe 経由で Postfix から呼び出し スコア 5.0 以上で件名に [SPAM] を付加 Spamhaus ZEN は OCI 環境でクエリ過多ブロックのため無効化 コンポーネント間インターフェース（Unix Socket・内部通信）\nLMTP：/var/spool/postfix/private/dovecot-lmtp（Postfix → Dovecot メール引き渡し） 4. 認証フロー # IMAP 認証（Roundcube → Dovecot）\nRoundcube が localhost:143 へ IMAP 接続し認証情報を送信 Dovecot が直接 /etc/dovecot/users を照合（ARGON2ID ハッシュ） OK → /var/mail/vmail/blackratel.com/\u0026lt;user\u0026gt;/ へのアクセスを許可 Roundcube ログイン\nブラウザから https://blackratel.com/xxxxxx/ にアクセス メールアドレスとパスワードを入力 Roundcube が Dovecot IMAP 認証を経由してログイン成功 5. メール受信フロー # 外部メールサーバー │ │ (1) SMTP (port 25) で接続 ▼ Postfix │ (2) virtual_mailbox_domains で blackratel.com を確認 │ (3) virtual_mailbox_maps で宛先ユーザーを確認 │ (4) content_filter で SpamAssassin (spamc pipe) へ転送 ▼ SpamAssassin (spamd) │ (5) スパムスコア算出・ヘッダー付加 │ (6) spamc -e sendmail で Postfix へ再投入（pickup 経由） ▼ Postfix (pickup・content_filter なし) │ (7) LMTP (Unix Socket) で Dovecot へ引き渡し ▼ Dovecot (LMTP) │ (8) Maildir 形式で保存 │ /var/mail/vmail/blackratel.com/\u0026lt;user\u0026gt;/new/ ▼ Roundcube │ (9) IMAP (localhost:143) で接続 │ (10) ブラウザでメールを閲覧 ▼ 閲覧完了 6. ユーザー管理方針 # OS ユーザーとの分離 # メールボックスの所有者となる専用 OS ユーザー vmail を1つだけ作成し、\nすべてのメールボックスをこのユーザーが所有する構成にする。\n種別 内容 OS ユーザー vmail 1つのみ（uid: 5000 / gid: 5000） シェル なし（SSH ログイン不可） メールユーザー /etc/dovecot/users で仮想的に管理 ユーザーファイルの形式 # # /etc/dovecot/users \u0026lt;user1\u0026gt;@blackratel.com:{ARGON2ID}xxxxxxxx:5000:5000::/var/mail/vmail/blackratel.com/\u0026lt;user1\u0026gt;:: \u0026lt;user2\u0026gt;@blackratel.com:{ARGON2ID}xxxxxxxx:5000:5000::/var/mail/vmail/blackratel.com/\u0026lt;user2\u0026gt;:: ユーザー追加の手順（運用時） # ① doveadm pw -s ARGON2ID でパスワードハッシュ生成 ② vault.yml に追記し、vars.yml の mail_users に追加 ③ ansible-playbook site.yml を実行 → Maildir ディレクトリ自動作成・Dovecot users ファイル更新 おまけ：ARGON2ID とは # ARGON2ID はパスワードハッシュ専用のアルゴリズムで、2015年の Password Hashing Competition で最優秀賞を受賞した。\n特徴 内容 メモリハード 大量メモリを消費する設計のため、GPU による総当たり攻撃が困難 調整可能コスト メモリ量・反復回数・並列度をパラメータで調整できる bcrypt との比較 bcrypt より強く、現時点で推奨されるパスワードハッシュ方式 Dovecot では doveadm pw -s ARGON2ID でハッシュを生成する。\n$ doveadm pw -s ARGON2ID Enter new password: **** Retype new password: **** {ARGON2ID}$argon2id$v=19$m=65536,t=3,p=1$xxxxxxxx$yyyyyyyy この出力をそのまま /etc/dovecot/users に貼り付けて使用する。\n","date":"May 3, 2026","externalUrl":null,"permalink":"/blog/posts/mailserverdesign.ja/","section":"Posts","summary":"Ubuntu 24.04 / Postfix / Dovecot / Roundcube / SpamAssassin による受信専用メールサーバー\n1. 要件 # 機能要件 # 項目 内容 メールドメイン blackratel.com 受信 外部メールサーバーから SMTP port 25 で受信 メール閲覧 IMAP port 143（localhost のみ）/ Roundcube WebUI スパムフィルタ SpamAssassin（content_filter 方式） 送信 対象外（受信専用） 認証 Dovecot 認証（IMAP・Roundcube ログイン） メールボックス形式 Maildir ユーザー管理 バーチャルユーザー（OS ユーザーと分離） 非機能要件 # 項目 内容 OS Ubuntu 24.04 LTS TLS 証明書 Postfix（port 25 STARTTLS）と Apache（HTTPS）のみ使用。Dovecot は localhost のみのため SSL 不要 パスワード保存 ARGON2ID ハッシュ ホスティング OCI Free Tier（アウトバウンド port 25 はブロックのため送信不可） スコープ外 # メール送信（OCI Free Tier のためアウトバウンド port 25 が利用不可） SPF / DKIM / DMARC 設定（送信しないため不要） 2. 環境・構成情報 # ミドルウェア # ソフトウェア 役割 Postfix MTA（外部からの受信） Dovecot MDA + MRA（メール保存・IMAP 提供） SpamAssassin スパムフィルタ（content_filter 方式） Roundcube WebUI（ブラウザでのメール閲覧） Apache2 Roundcube のリバースプロキシ ポート構成 # ポート プロトコル 用途 接続元 25 SMTP 外部メールサーバーからの受信 外部（OCI Security List + UFW で開放） 143 IMAP Roundcube → Dovecot（メール取得） localhost のみ 443 HTTPS Roundcube WebUI 外部 port 110 / 465 / 587 / 993 / 995 は無効化。\n","title":"メールサーバー(Postfix / Dovecot / Roundcube)","type":"posts"},{"content":"メールサーバー構築（Postfix / Dovecot / Roundcube / SpamAssassin）で発生したトラブルと解決策\n1. Roundcube: Internal Error（INBOX が存在しない） # 症状\nログインは成功するが画面に \u0026ldquo;Internal Error\u0026rdquo; が表示される。ブラウザの開発者ツールで確認したレスポンス：\n\u0026#34;exec\u0026#34;: \u0026#34;this.display_message(\\\u0026#34;サーバーエラー: STATUS: Internal error occurred.\\\u0026#34;, \\\u0026#34;error\\\u0026#34;, 0);\u0026#34; 原因\nIMAP の STATUS コマンドが失敗。メール未受信のため Maildir の INBOX ディレクトリが存在しなかった。エラーログには何も出力されないため原因特定にブラウザの開発者ツールが必要だった。\n解決策\nDovecot に INBOX 自動作成設定を追加した。初回ログイン時に INBOX ディレクトリが自動生成される。\n# dovecot 10-mail.conf namespace inbox { inbox = yes mailbox INBOX { auto = subscribe } } 2. Postfix: 外部送信タイムアウト（OCI Free Tier） # 症状\npostfix/smtp: connect to gmail-smtp-in.l.google.com[...]:25: Connection timed out 原因\nOCI Free Tier はアウトバウンド port 25 をブロックしている。\n解決策\n受信専用サーバーに方針変更。\n3. SpamAssassin: content_filter ループ # 症状\nメール受信時に同一メッセージのログが大量に出力され続ける。\n原因\nPostfix の content_filter は「受信メールを外部プログラムに渡してスキャンし、処理後に Postfix へ戻す」機構。SpamAssassin はスキャン後に spamc -e sendmail でメールを Postfix に再投入するが、この経路を担当する pickup デーモンにも content_filter 設定が引き継がれているため、再投入メールが再び SpamAssassin に送られ無限ループが発生した。\n# 正常系 smtpd → SpamAssassin → spamc -e sendmail → pickup → 配送 # ループ発生時 smtpd → SpamAssassin → spamc -e sendmail → pickup │ content_filter が再適用される │ SpamAssassin │ spamc -e sendmail → pickup → ...（無限ループ） 解決策\nmaster.cf の pickup サービスに -o content_filter= を追加し、再投入メールへのフィルタ適用を無効化した。\npickup unix n - y 60 1 pickup -o content_filter= -o receive_override_options=no_header_body_checks オプション 意味 -o content_filter= pickup 経由のメールは content_filter を空（無効）にする -o receive_override_options=no_header_body_checks ヘッダ・本文の再チェックも無効化し、二重処理を防ぐ pickup はローカルからの再投入専用デーモンなので、ここで content_filter を切っても外部着信メールには影響しない。smtpd 側の設定はそのまま有効なため、外部メールは引き続き SpamAssassin でスキャンされる。\n4. SpamAssassin: Spamhaus ZEN ブロック # SpamAssassin の概要 # SpamAssassin はメールをスコアリング方式でスパム判定するフィルタ。様々なルールを適用してスコアを合算し、閾値（デフォルト 5.0）を超えるとスパム判定する。\n受信メール │ ▼ ルール群を適用してスコア計算 ├─ ヘッダチェック（From/Subject の怪しいパターン等） +1.5 ├─ 本文チェック（スパムっぽい文言等） +2.0 ├─ DNSBL チェック ← 今回の問題箇所 +3.0 └─ SPF/DKIM チェック -0.1 │ ▼ 合計スコア ≥ 5.0 → スパム / \u0026lt; 5.0 → 正常メール DNSBL とは # DNSBL（DNS Block List）は「スパム送信元として知られている IP アドレスのブラックリスト」を DNS で照合できる仕組み。SpamAssassin は受信メールの送信元 IP を DNSBL サーバに問い合わせ、リストに載っていればスコアを加算する。\n# 照合の仕組み（送信元 IP: 1.2.3.4 の場合） DNS クエリ: 4.3.2.1.zen.spamhaus.org ├─ 応答あり → リストに載っている → スコア加算（スパム寄り） └─ NXDOMAIN → リストにない → スコア変動なし Spamhaus ZEN とは # Spamhaus が提供する複数のブロックリストを統合した DNSBL。\nリスト 対象 SBL 確認済みスパム送信者の IP XBL マルウェア・ボットネットに乗っ取られた IP PBL ISP が「メール送信に使うべきでない」と申告した IP（住宅向け回線等） ZEN はこれら全てをまとめて一度に照合できるリスト。\n症状\ndns_block_rule RCVD_IN_ZEN_BLOCKED hit (This means DNSBL blocked you due to too many queries) 原因\nSpamhaus ZEN は本来「受信メールの送信元 IP」を照合するサービスだが、クエリ自体の送信元（＝自サーバーの IP）もチェックしている。Spamhaus ZEN は無償利用に制限があり、OCI などクラウドプロバイダの IP からの DNS クエリをブロックする。受信メールの内容や送信元 IP とは無関係で、クエリを送る自サーバーの IP が原因。\n通常の利用（ISP 等） 自サーバー(IP: A) → DNS クエリ → Spamhaus ZEN 「受信メールの送信元 IP: X がリストに載っているか？」 OCI 環境で起きたこと 自サーバー(OCI IP) → DNS クエリ → Spamhaus ZEN │ 「クエリ元 OCI の IP 自体がブロック対象」 │ dns_block_rule ヒット 解決策\nlocal.cf に Spamhaus ZEN への DNS クエリを無効化する設定を追加した。\ndns_query_restriction deny zen.spamhaus.org これにより SpamAssassin が zen.spamhaus.org へのクエリを送らなくなる。Spamhaus ZEN によるスコアリングは無効になるが、ヘッダ・本文・SPF/DKIM 等の他ルールは引き続き機能する。根本対応としては Spamhaus の有償ライセンス取得やローカルミラーの構築があるが、個人運用の受信専用サーバーであれば無効化で十分。\n","date":"May 3, 2026","externalUrl":null,"permalink":"/blog/posts/troubleshooting.ja/","section":"Posts","summary":"メールサーバー構築（Postfix / Dovecot / Roundcube / SpamAssassin）で発生したトラブルと解決策\n1. Roundcube: Internal Error（INBOX が存在しない） # 症状\nログインは成功するが画面に “Internal Error” が表示される。ブラウザの開発者ツールで確認したレスポンス：\n\"exec\": \"this.display_message(\\\"サーバーエラー: STATUS: Internal error occurred.\\\", \\\"error\\\", 0);\" 原因\nIMAP の STATUS コマンドが失敗。メール未受信のため Maildir の INBOX ディレクトリが存在しなかった。エラーログには何も出力されないため原因特定にブラウザの開発者ツールが必要だった。\n解決策\nDovecot に INBOX 自動作成設定を追加した。初回ログイン時に INBOX ディレクトリが自動生成される。\n# dovecot 10-mail.conf namespace inbox { inbox = yes mailbox INBOX { auto = subscribe } } 2. Postfix: 外部送信タイムアウト（OCI Free Tier） # 症状\n","title":"メールサーバー構築 トラブルシューティング記録","type":"posts"},{"content":"","date":"April 29, 2026","externalUrl":null,"permalink":"/blog/tags/android/","section":"Tags","summary":"","title":"Android","type":"tags"},{"content":"","date":"April 29, 2026","externalUrl":null,"permalink":"/blog/tags/%E3%82%A2%E3%83%97%E3%83%AA/","section":"Tags","summary":"","title":"アプリ","type":"tags"},{"content":"","date":"April 29, 2026","externalUrl":null,"permalink":"/blog/categories/%E3%82%A2%E3%83%97%E3%83%AA%E7%B4%B9%E4%BB%8B/","section":"Categories","summary":"","title":"アプリ紹介","type":"categories"},{"content":"スロット要素を組み合わせたブロック崩しゲーム(slot brick breaker)を作りました。 リラックスモードだと、アイテムがたくさん落ちるので爽快でサクサク遊べます。 物足りない人は、ハードモードを挑戦してください。\nブロックを崩しながら、アイテムやスロットでパワーアップしよう——シンプルだけど何度でも遊びたくなる一本です。\n遊び方 # 画面下のパドルを左右に動かしてボールを跳ね返す すべてのブロックを崩せばステージクリア 特徴 # スロットでパワーアップ ボールが通過するとスロットを回せます。マルチボール・貫通弾・巨大ボールなど、運次第で強力な効果が手に入ります。\nシンプルな操作 パドルをスライドするだけ。直感的に遊べます。\nGoogle Play からダウンロード\n","date":"April 29, 2026","externalUrl":null,"permalink":"/blog/posts/blockbreaker.ja/","section":"Posts","summary":"スロット要素を組み合わせたブロック崩しゲーム(slot brick breaker)を作りました。 リラックスモードだと、アイテムがたくさん落ちるので爽快でサクサク遊べます。 物足りない人は、ハードモードを挑戦してください。\nブロックを崩しながら、アイテムやスロットでパワーアップしよう——シンプルだけど何度でも遊びたくなる一本です。\n遊び方 # 画面下のパドルを左右に動かしてボールを跳ね返す すべてのブロックを崩せばステージクリア 特徴 # スロットでパワーアップ ボールが通過するとスロットを回せます。マルチボール・貫通弾・巨大ボールなど、運次第で強力な効果が手に入ります。\nシンプルな操作 パドルをスライドするだけ。直感的に遊べます。\nGoogle Play からダウンロード\n","title":"スロット×ブロック崩しアプリを作りました","type":"posts"},{"content":"","date":"April 27, 2026","externalUrl":null,"permalink":"/blog/tags/%E3%83%91%E3%82%BA%E3%83%AB/","section":"Tags","summary":"","title":"パズル","type":"tags"},{"content":"","date":"April 27, 2026","externalUrl":null,"permalink":"/blog/tags/%E5%AE%9D%E7%9F%B3/","section":"Tags","summary":"","title":"宝石","type":"tags"},{"content":"宝石のソートパズルゲーム(gem sort puzzle)を作りました。よかったら遊んでみてください。\nバラバラになった宝石を、同じ色ごとに水晶ボックスへ——じっくり考えながら解いていくパズルです。\nステージとステージの間に広告が入らないので、集中して遊び続けられます。\n遊び方 # 水晶ボックスをタップして一番上の宝石を選び、移動先の試験管をタップ 同じ色の上か、空の水晶ボックスにしか置けない すべての水晶ボックスを同じ色にすればステージクリア！ 特徴 # シンプルな操作 タップで動かすだけ。すぐに始められます。\n自然に上がる難易度 ステージが進むにつれて水晶ボックスの数や宝石の格納数が増えていきます。急に難しくなるのではなく、じわじわと難しくなります。\nカスタマイズ クリア時のエフェクトを変更できます。遊びながらポイントを貯めて、自分好みに整えてください。\nGoogle Play からダウンロード\n","date":"April 27, 2026","externalUrl":null,"permalink":"/blog/posts/gemsortpuzzle.ja/","section":"Posts","summary":"宝石のソートパズルゲーム(gem sort puzzle)を作りました。よかったら遊んでみてください。\nバラバラになった宝石を、同じ色ごとに水晶ボックスへ——じっくり考えながら解いていくパズルです。\nステージとステージの間に広告が入らないので、集中して遊び続けられます。\n遊び方 # 水晶ボックスをタップして一番上の宝石を選び、移動先の試験管をタップ 同じ色の上か、空の水晶ボックスにしか置けない すべての水晶ボックスを同じ色にすればステージクリア！ 特徴 # シンプルな操作 タップで動かすだけ。すぐに始められます。\n自然に上がる難易度 ステージが進むにつれて水晶ボックスの数や宝石の格納数が増えていきます。急に難しくなるのではなく、じわじわと難しくなります。\nカスタマイズ クリア時のエフェクトを変更できます。遊びながらポイントを貯めて、自分好みに整えてください。\nGoogle Play からダウンロード\n","title":"宝石ソートパズルアプリを作りました","type":"posts"},{"content":" はじめに # ブログを始めるにあたって、まず悩んだのがプラットフォームの選定だ。\nWordPress をはじめとする CMS は機能が豊富な反面、DB・PHP・プラグイン管理など 運用コストが高い。今回は「書くことに集中できる、軽量で安全な構成」を目指した。\nその結果として選んだのが Hugo だ。\nなぜ Hugo を選んだのか # 1. セキュリティ # Hugo は静的サイトジェネレーター（SSG）であり、ビルド時にすべてのページを HTML ファイルとして生成する。つまり：\nSQL インジェクション不可 php等の言語の脆弱性と無縁 さらに Hugo は 単一の Go バイナリとして配布されており、 npm のようなサプライチェーンリスクもない。 依存パッケージがゼロというのは、セキュリティ観点では大きな強みだ。\n2. リソース効率 # Web サーバー側に求められるのは「静的ファイルを返すだけ」の能力のみ。 Nginx の最小構成で十分動作し、メモリ・CPU ともにほぼ消費しない。\nWordPress のような動的 CMS と比べると、1 リクエストあたりの処理が DB クエリ → PHP 実行 → レンダリング から、ただのファイル読み込みになる。\n3. SEO への対応 # Hugo はビルド時に以下を自動生成する：\n機能 詳細 サイトマップ /blog/sitemap.xml RSS フィード /blog/index.xml メタタグ title / description / OGP パーマリンク 柔軟に設定可能 静的 HTML は検索エンジンのクローラーが即座に解析できるため、 Core Web Vitals のスコアも出しやすい。\n4. 記事記載 # 記事は Markdown で書く。フォーマットに悩まず内容に集中できる。 ローカルで hugo server を起動しておけば、ファイルを保存するたびに ブラウザがリアルタイムで更新される。\nセットアップの概要 # 環境は以下の 2 台構成だ：\n[ローカルサーバ] 執筆・プレビュー（hugo server） ↓ rsync [Web サーバ] 静的ファイル配信のみ（Nginx） インストール・初期構築は Ansible で自動化 # 手順を再現可能にするため、ローカルサーバのセットアップは Ansible Playbook として管理している。\n主な処理の流れは下記のとおりだ：\nHugo 専用ユーザーを作成（権限分離） Hugo Extended バイナリを GitHub Releases から取得・配置 hugo new site でプロジェクトを初期化 PaperMod テーマを git submodule として追加 hugo server を systemd サービスとして登録・自動起動 Hugo のバージョン # 執筆時点での最新 stable は v0.160.1（2026-04-08 リリース）。\nURL 構成 # 既存コンテンツとの共存を考慮し、ブログは /blog/ 配下に配置した：\nhttps://blackratel.com/ # 既存コンテンツ（Hugo 管理外） https://blackratel.com/blog/ # Hugo 管理ブログ hugo.toml の baseURL を https://blackratel.com/blog/ に設定することで、 サイトマップや内部リンクが正しく生成される。\nまとめ # 観点 Hugo の評価 セキュリティ ◎ 依存ゼロ・DB なし・攻撃面最小 運用コスト ◎ Web サーバは静的配信のみ SEO ○ サイトマップ・メタタグ自動生成 執筆体験 ○ Markdown・リアルタイムプレビュー 学習コスト △ Go テンプレート構文はやや独特 個人ブログや技術情報の発信用途であれば、Hugo は現時点で最もバランスの 取れた選択肢の一つだと感じている。\n","date":"April 26, 2026","externalUrl":null,"permalink":"/blog/posts/why-hugo.ja/","section":"Posts","summary":"はじめに # ブログを始めるにあたって、まず悩んだのがプラットフォームの選定だ。\nWordPress をはじめとする CMS は機能が豊富な反面、DB・PHP・プラグイン管理など 運用コストが高い。今回は「書くことに集中できる、軽量で安全な構成」を目指した。\nその結果として選んだのが Hugo だ。\nなぜ Hugo を選んだのか # 1. セキュリティ # Hugo は静的サイトジェネレーター（SSG）であり、ビルド時にすべてのページを HTML ファイルとして生成する。つまり：\nSQL インジェクション不可 php等の言語の脆弱性と無縁 さらに Hugo は 単一の Go バイナリとして配布されており、 npm のようなサプライチェーンリスクもない。 依存パッケージがゼロというのは、セキュリティ観点では大きな強みだ。\n","title":"このブログを Hugo で構築した理由","type":"posts"},{"content":"","date":"April 26, 2026","externalUrl":null,"permalink":"/blog/tags/%E3%81%9A%E3%82%93%E3%81%A0%E3%82%82%E3%82%93/","section":"Tags","summary":"","title":"ずんだもん","type":"tags"},{"content":"","date":"April 26, 2026","externalUrl":null,"permalink":"/blog/categories/%E3%83%96%E3%83%AD%E3%82%B0%E6%A7%8B%E7%AF%89/","section":"Categories","summary":"","title":"ブログ構築","type":"categories"},{"content":"","date":"April 26, 2026","externalUrl":null,"permalink":"/blog/tags/%E5%8D%A0%E3%81%84/","section":"Tags","summary":"","title":"占い","type":"tags"},{"content":"宝石カードを使った占いアプリを作りました。よかったら使ってみてください。\n朝のちょっとした時間に、大切な決断の前に、恋のことで迷った時に——そんな場面で、そっと背中を押してくれるアプリを目指しました。\nできること # 3種類の占いが使えます。\n朝の1分占い 朝、今日の指針をカードに聞いてみる。それだけでなんとなく気持ちが整う気がします。\n恋愛診断 恋のことで迷った時に。答えを出すというより、自分の気持ちを整理するヒントになればと思っています。\n3枚カードリーディング 過去・現在・未来の流れを3枚のカードで読みます。少し込み入ったことを考えたい時に。\nカードの選び方 # 指で画面上に円を描くようになぞると、自然と1枚が選ばれます。論理的に選ぶのではなく、その瞬間の直感に任せる仕組みです。\n34種類の宝石カードがあり、それぞれに固有のメッセージがあります。\nシンプルに使えることを意識して作ったので、完全無料・課金なしです。\n気が向いたら使ってみてください。\nGoogle Play からダウンロード\n","date":"April 26, 2026","externalUrl":null,"permalink":"/blog/posts/oraclecard01.ja/","section":"Posts","summary":"宝石カードを使った占いアプリを作りました。よかったら使ってみてください。\n朝のちょっとした時間に、大切な決断の前に、恋のことで迷った時に——そんな場面で、そっと背中を押してくれるアプリを目指しました。\nできること # 3種類の占いが使えます。\n朝の1分占い 朝、今日の指針をカードに聞いてみる。それだけでなんとなく気持ちが整う気がします。\n恋愛診断 恋のことで迷った時に。答えを出すというより、自分の気持ちを整理するヒントになればと思っています。\n3枚カードリーディング 過去・現在・未来の流れを3枚のカードで読みます。少し込み入ったことを考えたい時に。\nカードの選び方 # 指で画面上に円を描くようになぞると、自然と1枚が選ばれます。論理的に選ぶのではなく、その瞬間の直感に任せる仕組みです。\n34種類の宝石カードがあり、それぞれに固有のメッセージがあります。\nシンプルに使えることを意識して作ったので、完全無料・課金なしです。\n気が向いたら使ってみてください。\nGoogle Play からダウンロード\n","title":"宝石占いアプリを作りました","type":"posts"},{"content":"","date":"April 26, 2026","externalUrl":null,"permalink":"/blog/tags/%E9%9D%99%E7%9A%84%E3%82%B5%E3%82%A4%E3%83%88/","section":"Tags","summary":"","title":"静的サイト","type":"tags"},{"content":"","date":"April 26, 2026","externalUrl":null,"permalink":"/blog/tags/%E9%BA%BB%E9%9B%80/","section":"Tags","summary":"","title":"麻雀","type":"tags"},{"content":"麻雀の点数計算アプリを作りました。よかったら使ってみてください。\n「この手、何点？」——対局中にそんな疑問が浮かんだ時、すぐに答えが出るアプリがほしくて作りました。役・点数を表示してくれるのはもちろん、ずんだもんが音声で読み上げてくれるのが一番の特徴です。\n使い方 # 鳴きがある場合はまず、暗カン・明カン・ポン・チーの数を入力します。続いて牌画像をタップして手牌を入力。\n親・子、自風・場風、リーチ・一発の有無を選択します。\nツモ・海底などのあがり方と、ドラ（ドラ・裏ドラ・赤ドラすべての合計）の枚数を設定。最後にあがり牌を選択します。\nすると、成立した役と点数が一覧で表示されます。そして——ずんだもんが役名と得点を読み上げてくれます。対局中、画面を見ずに耳で確認できるのは地味に便利です。\n符計算の詳細内訳（底符・面子符・雀頭符・待ち符など）も表示できるので、「なぜこの点数になるのか」を確認しながら覚えることもできます。\n音声はON/OFFを切り替えられます。喰いタンのあり・なし設定にも対応しています。\n無料で使えます。気が向いたらぜひ。\nGoogle Play からダウンロード\n音声合成: VOICEVOX:ずんだもん\n","date":"April 26, 2026","externalUrl":null,"permalink":"/blog/posts/mahjongcalc.ja/","section":"Posts","summary":"麻雀の点数計算アプリを作りました。よかったら使ってみてください。\n「この手、何点？」——対局中にそんな疑問が浮かんだ時、すぐに答えが出るアプリがほしくて作りました。役・点数を表示してくれるのはもちろん、ずんだもんが音声で読み上げてくれるのが一番の特徴です。\n使い方 # 鳴きがある場合はまず、暗カン・明カン・ポン・チーの数を入力します。続いて牌画像をタップして手牌を入力。\n親・子、自風・場風、リーチ・一発の有無を選択します。\nツモ・海底などのあがり方と、ドラ（ドラ・裏ドラ・赤ドラすべての合計）の枚数を設定。最後にあがり牌を選択します。\nすると、成立した役と点数が一覧で表示されます。そして——ずんだもんが役名と得点を読み上げてくれます。対局中、画面を見ずに耳で確認できるのは地味に便利です。\n符計算の詳細内訳（底符・面子符・雀頭符・待ち符など）も表示できるので、「なぜこの点数になるのか」を確認しながら覚えることもできます。\n音声はON/OFFを切り替えられます。喰いタンのあり・なし設定にも対応しています。\n無料で使えます。気が向いたらぜひ。\nGoogle Play からダウンロード\n音声合成: VOICEVOX:ずんだもん\n","title":"麻雀点数計算アプリを作りました","type":"posts"},{"content":"","externalUrl":null,"permalink":"/blog/archives/","section":"Archives","summary":"","title":"Archives","type":"archives"},{"content":"","externalUrl":null,"permalink":"/blog/_index.ja/","section":"Home","summary":"","title":"ホーム","type":"page"}]