# takeokunn's blog > -------------------------------------------------------------------------------- title: "nix-darwinのlinux-builderでNixOSを動かす" date: "2026-01-10" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 元々OrbStack VMでNixOSを動かしていたのだが、k8sを動かす時にOrbStackだと動かずDocker for Macだと動くという事象が発生した。 調査をした所、nix-darwinがlinux-builderというものをサポートしてることがわかったのでメモしておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. linux-builderについて調査 {#1-dot-linux-builderについて調査} このあたりに纏まっている。 - [nix linux-builder - scrapbox](https://scrapbox.io/kanna/nix_linux-builder) - [Build and Deploy Linux Systems from macOS](https://nixcademy.com/posts/macos-linux-builder/) nix-darwinには [modules/nix/linux-builder.nix](https://github.com/nix-darwin/nix-darwin/blob/master/modules/nix/linux-builder.nix) が提供されており、 `enable=true` するだけで `launchd.daemons` に登録されるようだ。 ### 2. nix-darwinを導入してみる {#2-dot-nix-darwinを導入してみる} 現状の設定は以下。 ```nix { lib, ... }: { nix.linux-builder = { enable = true; ephemeral = false; systems = [ "aarch64-linux" "x86_64-linux" ]; config = { boot.binfmt.emulatedSystems = [ "x86_64-linux" ]; virtualisation = { cores = 6; memorySize = lib.mkForce (1024 * 16); diskSize = lib.mkForce (1024 * 200); }; nix.settings = { experimental-features = [ "nix-command" "flakes" ]; }; security.sudo.extraRules = [ { users = [ "builder" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; } ]; }; }; } ``` ### 3. 動いてることを確認する {#3-dot-動いてることを確認する} `take` 部分はwhoamiに読み替えてください。 ```console $ sudo chmod 644 /etc/nix/builder_ed25519.pub $ sudo chmod 600 /etc/nix/builder_ed25519 $ sudo chown take:staff /etc/nix/builder_ed25519 ``` あとはsshするだけ。 ```console $ ssh linux-builder ``` ## 得られた結果・所感 {#得られた結果-所感} sshしてsudoできるようになった。 {{< figure src="/images/455BF273-2F6B-4D49-8179-618E431DE1F6.png" >}} ## 今後の展開・検討事項 {#今後の展開-検討事項} deploy-rsと組み合わせてnixos-container運用を固めきりたい。 現状だととりあえず動いただけなのでlinux-builder自体の深堀もしたい。 -------------------------------------------------------------------------------- title: "2025年12月 個人的claude codeワークフロー" date: "2025-12-30" -------------------------------------------------------------------------------- ## 1. はじめに {#1-dot-はじめに} Claude Codeを本格的に使い始めました。 最初はGitHub Copilotと同じ感覚で、コード生成ツールの延長として使っていたのですが、あるとき気づきました。「セキュリティレビューをお願いしたら、コードスタイルの指摘ばかり返ってきた」「昨日と同じ質問をしたのに、まったく違うフォーマットで回答された」「プロジェクトの規約を毎回説明するのが面倒」。これ、Claude Codeの問題じゃなくて、自分の使い方の問題でした。 試行錯誤を重ねるうちに、Claude Codeは単なるコード補完ツールではなく、適切に設定すればマルチエージェントシステムとして機能することに気づきました。 この記事では、構築したワークフローを共有します。 ちなみに、この記事も大半はClaude Codeに書いてもらいました。 ## 2. 背景と設計哲学 {#2-dot-背景と設計哲学} ### 2.1. 感じていた課題 {#2-dot-1-dot-感じていた課題} Claude Codeを本格的に使い始める前、いくつかの課題を感じていました。 一番大きかったのは専門性の欠如です。単一のAIエージェントでは、コード品質・セキュリティ・テスト・ドキュメントなど複数のドメインにまたがるタスクをうまく処理できませんでした。セキュリティの観点でレビューをお願いしても、コードスタイルの指摘が混在して、本当に確認したいポイントが埋もれてしまいます。 出力の不安定性も悩みの種でした。構造化されたプロンプトなしでは、AIの応答品質やフォーマットが毎回異なります。昨日と同じ質問をしても、回答の形式がまったく違うということがよくありました。 繰り返しの説明にも時間を取られていました。同じパターンや好みを何度もAIに説明する必要があり、徒労感がありました。 ### 2.2. 5つのルール {#2-dot-2-dot-5つのルール} CLAUDE.mdには5つの `critical` ルールを定義しています。この5つが私のワークフローの根幹になっています。 1. サブエージェントへの委譲 : 親エージェントは方針決定とオーケストレーションに集中し、詳細な作業は専門エージェントに任せる 2. Serenaメモリの事前確認 : 実装前に必ず `list_memories` と `read_memory` でプロジェクトの規約やパターンを確認する 3. シンボルレベルの操作 : ファイル全体を読み込むのではなく、Serenaの `find_symbol` などでシンボル単位で操作する 4. perlでテキスト処理 : sed/awkは使わない。 `perl -pe 's/foo/bar/g'` で統一 5. 英語で出力 : 出力は常に英語に統一 なぜこの5つなのか。1と2はマルチエージェント構成の核心、3はトークン効率の改善、4と5は出力の一貫性を担保するためです。 ## 3. プロンプト設計の基盤 {#3-dot-プロンプト設計の基盤} ここで紹介するプロンプトの多くは、Claude Code自身に生成させました。「こういう形式で書いて」と伝えて、その出力を調整しながら現在の形に至りました。 Claude Codeのプロンプトは構造化されたXMLで記述しています。 ### 3.1. XMLによる構造化プロンプト {#3-dot-1-dot-xmlによる構造化プロンプト} #### 3.1.1. なぜXMLなのか {#3-dot-1-dot-1-dot-なぜxmlなのか} Markdownやプレーンテキストではなく、XMLを採用しました。これは公式のベストプラクティスではなく、個人的な経験に基づく選択です。 [Anthropicの公式ドキュメント](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/use-xml-tags)では、XMLタグについて次のように説明されています: > There are no canonical "best" XML tags that Claude has been trained with in particular, although we recommend that your tag names make sense with the information they surround. つまり、XMLタグに特別な訓練がされているわけではなく、タグ名は内容に合った意味を持たせることが推奨されています。XMLが必須というわけではありません。 XMLを選んだ理由は3つあります。 まず構造的な表現力。ネストした階層構造、属性による修飾、明確な開始・終了タグにより、複雑な指示を表現しやすいです。 次に指示の遵守率。XMLで書いた指示のほうが意図とおりに解釈されることが多いという肌感覚があります。Markdownの見出しより、XMLタグのほうがセクションの境界として認識されやすい印象です。 そしてスキーマによる一貫性。決まった要素名と構造を使うことで、commands/agents/skills間でプロンプトの品質を均一に保てます。 #### 3.1.2. XMLコア要素 {#3-dot-1-dot-2-dot-xmlコア要素} 主要な要素は4つあります。 `` は役割を1段落で定義します。これがエージェントの「存在理由」になります。たとえば親オーケストレーターであれば「ポリシー決定、判断、要件定義を担当し、詳細な実行作業は専門サブエージェントに委譲する」といった形で記述します。 ` Delegate detailed work to sub-agents; focus on orchestration Always check Serena memories before implementation Use symbol-level operations over reading entire files Use perl for all text processing; never use sed or awk Always output in English ``` これが実際の私のCLAUDE.mdに書いてある5つのクリティカルルールです。 `standard` 優先度のルールも同様に定義できます。GitHub操作には `gh` コマンドを使う、Context7 MCPでドキュメントを確認する、といった推奨事項を記述しています。 `` はフェーズベースの作業フローを定義します。各フェーズは `` 要素を含みます。フェーズ名は `task_analysis` / `delegation` / `analyze` / `investigate` / `execute` / `verify` / `document` のように行動指向で統一しています。 `` は明示的な「やるべきこと」と「避けるべきこと」を `` と `` で定義します。たとえば「実装前にメモリを確認する」「perlでテキスト処理する」といった必須事項と、「ファイル全体を読み込まない」「sed/awkを使わない」といった禁止事項を明記します。 #### 3.1.3. YAMLフロントマター {#3-dot-1-dot-3-dot-yamlフロントマター} CommandsとSkillsには、YAMLフロントマターでメタデータを付与しています。Commandsでは `argument-hint` と `description` を使い、引数のヒントとコマンドの説明を定義します。Skillsでは `name`, `description`, `version` を使い、スキル名と使用場面の説明、バージョン情報を記述します。 なお、CLAUDE.md(グローバル設定)とAgentsファイルにはYAMLフロントマターを使用せず、直接XMLで始める運用にしています。 ### 3.2. CLAUDE.mdによるグローバル設定 {#3-dot-2-dot-claude-dot-mdによるグローバル設定} CLAUDE.mdは `~/.claude/CLAUDE.md` に配置するグローバル設定ファイルです。ここにXML形式でプロンプトを記述し、Claude Codeの基本的な振る舞いを定義します。 #### 3.2.1. グローバル vs プロジェクト {#3-dot-2-dot-1-dot-グローバル-vs-プロジェクト} Claude Codeは2種類のCLAUDE.mdを認識します。 | 種類 | パス | 適用範囲 | |------|---------------------|--------| | グローバル | `~/.claude/CLAUDE.md` | 全セッション | | プロジェクト | `/CLAUDE.md` | 該当プロジェクトのみ | 私の運用では、グローバルCLAUDE.mdに「親オーケストレーター」としての人格を定義しています。委譲のポリシー、使用するMCPツール、出力言語などの共通設定をここに集約し、プロジェクト固有の情報(技術スタック、コーディング規約など)は各プロジェクトのCLAUDE.mdに記述します。 #### 3.2.2. デフォルト動作との比較 {#3-dot-2-dot-2-dot-デフォルト動作との比較} カスタマイズによってClaude Codeの振る舞いは大きく変わります。 | 観点 | デフォルト | カスタマイズ後 | |------|-----------|--------------| | 役割 | 汎用コーディングアシスタント | Orchestration Agent | | 作業方針 | 自ら直接実行 | sub-agentへ委譲 | | ファイル操作 | ファイル全体を読み込む | シンボルレベルで操作 | | テキスト処理 | sed/awkを使用 | perl強制 | | 出力言語 | 入力言語に依存 | 英語に統一 | | 知識管理 | セッション内で完結 | Serenaメモリを参照 | デフォルトは「何でも自分でやる」汎用アシスタントです。カスタマイズ後は「方針を決めて専門家に任せる」オーケストレーターになります。 ## 4. マルチエージェント構成の実践 {#4-dot-マルチエージェント構成の実践} 私の設定の最大の特徴は、Claude Codeをマルチエージェントシステムとして扱っている点です。Anthropicも[マルチエージェントリサーチシステム](https://www.anthropic.com/engineering/multi-agent-research-system)や[Claude Codeのベストプラクティス](https://www.anthropic.com/engineering/claude-code-best-practices)で、オーケストレーター・ワーカーパターンの有効性を示しています。 ### 4.1. 階層構造の概要 {#4-dot-1-dot-階層構造の概要} {{< figure src="/images/CC3ED851-5E06-4A5C-BC6A-1DEF112BE45E.png" >}} 各階層の役割を整理します。CLAUDE.mdは親オーケストレーターとして方針決定と委譲を担います。Commandsはユーザーインターフェースとして特定タスクの起点となります。Agentsはdesign、security、testなど専門領域のエキスパートとして機能します。Skillsはnix-ecosystem、serena-usageなどドメイン知識ベースを提供します。 このマルチエージェント構成には大きなメリットがあります。まず関心の分離により、各エージェントは自分の専門領域に集中できます。セキュリティエージェントはセキュリティだけ、テストエージェントはテストだけを考えればよいのです。次に並列実行が可能になり、独立したタスクを同時に処理できます。また知識の再利用として、Skillsは複数のAgentsから参照されるため、同じ知識を何度も定義する必要がありません。そして保守性の向上により、1つのエージェントの変更が他に影響しないという利点があります。 ### 4.2. コマンドによるワークフロー定義 {#4-dot-2-dot-コマンドによるワークフロー定義} Claude Codeのcommandは、AIエージェントの振る舞いを定義する重要な設定です。私はカスタムコマンドを設計し([commands/ディレクトリ参照](https://github.com/takeokunn/nixos-configuration/tree/af95701a89e8128ba000dee3062eefa33c7d2001/home-manager/programs/claude-code/commands))、「オーケストレーションパターン」と「Readonlyファースト哲学」という2つの設計思想に基づいて構築しています。 #### 4.2.1. Readonlyファースト哲学 {#4-dot-2-dot-1-dot-readonlyファースト哲学} 「まず調べて、実行計画を立ててから、手を動かす」がReadonlyファースト哲学の核心です。 AIに作業を任せる際、もっとも危険なのは「調査しているつもりが、いつの間にかコードを変更していた」という事故です。この問題を根本から解決するため、私のワークフローではコマンドを「調査系」と「実行系」に完全分離しています。 `/define` (要件定義)、 `/ask` (質問・調査)、 `/bug` (原因調査)の3つは、どれだけ深く調べても絶対にファイルを変更しません。各コマンドのプロンプトで `}} #### 4.2.4. 代表的なコマンド: /define {#4-dot-2-dot-4-dot-代表的なコマンド-define} `/define` は実装前に詳細な要件を定義するコマンドです。技術的制約、設計方針、仕様を明確化します。クリティカルルールとして「ファイルを変更しない」「コードを実装しない」「技術的に不可能な要求を明確に識別する」を設定しています。 Workflowは「Analyze → Investigate → Clarify → Verify → Document」の5フェーズで構成し、「要件ドキュメント」と「タスク分解」の2つの成果物を生成します。このコマンドを使うことで、実装を始める前に要件を整理でき、手戻りを減らせるようになりました。 以下は実際のプロンプト構造です(簡略版): ```xml Conduct detailed requirements definition before implementation, clarifying technical constraints, design policies, and specifications. Never modify, create, or delete files Never implement code; requirements definition only Clearly identify technically impossible requests What is the user requesting? What technical constraints exist? Delegate to explore agent: find relevant files Delegate to design agent: evaluate architecture Score questions by: design branching, irreversibility (1-5) Present high-score questions first Create comprehensive requirements document Break down tasks for /execute handoff ``` ポイントは `readonly`"true"= 属性です。サブエージェントにも読み取り専用を強制することで、調査フェーズ全体での安全性を担保しています。これがあるおかげで、「調べてたらうっかりファイル消しちゃいました」みたいな事故を防げます。 ### 4.3. 専門エージェントの設計 {#4-dot-3-dot-専門エージェントの設計} agentsディレクトリには複数の専門エージェントを定義しています([agents/ディレクトリ参照](https://github.com/takeokunn/nixos-configuration/tree/af95701a89e8128ba000dee3062eefa33c7d2001/home-manager/programs/claude-code/agents))。各エージェントはSingle Responsibility Principleに基づき、1つのドメインに特化したエキスパートとして振る舞います。 #### 4.3.1. 設計思想 {#4-dot-3-dot-1-dot-設計思想} エージェント設計の核心は3点あります。 まずドメイン特化です。各エージェントは自分の専門領域のみを担当します。セキュリティエージェントはセキュリティだけ、テストエージェントはテストだけを扱います。この明確な責任分担により、専門性の高い出力が得られます。 次にワークフローの統一があります。全エージェントが「analyze → (domain-specific phases) → report」という基本構造を持ちます。フェーズ数はエージェントにより4〜5で異なりますが(例: security agentは「analyze → gather → scan → remediate → report」、design agentは「analyze → investigate → synthesize → document」)、最初の分析と最後の報告という骨格は共通です。これにより振る舞いが予測可能になり、どのエージェントを使っても一貫した体験が得られます。 そして安全性のガードレールです。 ` Expert security agent for vulnerability detection, remediation, and dependency management. Specializes in authentication, injection attacks, secret leakage, encryption, and dependency vulnerabilities. Alert immediately on secret leakage detection Stop build on critical vulnerabilities Verify context before concluding vulnerability exists Use existing audit tools (npm audit, cargo audit) What are the high-risk files/areas? What authentication/authorization patterns exist? Are there hardcoded secrets? Identify high-risk files, check dependencies Pattern match secrets/injections, run audits Auto-fix or report, verify changes Summary by severity with fixes Scan for hardcoded API keys 1. Search for API key patterns with serena search_for_pattern 2. Check config files for hardcoded values 3. Verify if values are actual secrets or placeholders { "status": "warning", "summary": "2 hardcoded API keys detected", "details": [{"error": "SEC002", "location": "/config.js:15", "fix_suggestion": "Use process.env.API_KEY"}], "next_actions": ["Migrate to env vars"] } Stop build, alert Alert immediately Recommend update ``` ポイントは `` 要素です。入力・処理手順・出力の3つを明示することで、エージェントの期待動作を具体的に伝えています。「こういうときはこう動いてね」というお手本を見せてあげるイメージです。また、 `` でエラー種別を定義し、状況に応じた適切な対応を指示しています。 ### 4.4. スキルによる知識ベース構築 {#4-dot-4-dot-スキルによる知識ベース構築} Skillsはドメイン知識ベースとして機能し、複数のAgentsから参照される共有リソースです。Agentsが「専門領域のエキスパート」なら、Skillsは「ドメイン知識の辞書」といえます。 #### 4.4.1. Skillの役割と位置づけ {#4-dot-4-dot-1-dot-skillの役割と位置づけ} 階層構造は「Commands → Agents → Skills」となっています。Commandsがユーザーインターフェースとして機能し、Agentsを呼び出します。Agentsは専門領域のタスクを実行し、Skillsを参照します。Skillsはドメイン知識を提供し、複数のAgentsから参照されます。 この設計の重要なポイントは、Skillsが状態を持たない純粋な知識ベースであることです。Agentsがタスクを実行する際に必要な「パターン」「ベストプラクティス」「アンチパターン」を提供し、Agents自身は実行に集中できます。 #### 4.4.2. スキルカテゴリ一覧 {#4-dot-4-dot-2-dot-スキルカテゴリ一覧} 現在複数のスキルを4カテゴリに分類して定義しています([skills/ディレクトリ参照](https://github.com/takeokunn/nixos-configuration/tree/af95701a89e8128ba000dee3062eefa33c7d2001/home-manager/programs/claude-code/skills))。 ツール連携では、serena-usageとcontext7-usageでMCPの使い方を定義しています。言語・インフラエコシステムでは、Nix、TypeScript、Go、Rust、Common Lisp、Emacs Lispの6言語に加え、aws-ecosystemでAWS CLI/Terraformのパターンもカバーしています。私が日常的に使う技術スタックを網羅しているため、どの環境で開発してもClaude Codeが適切な知識を持って対応できます。ワークフローでは、investigation-patterns、execution-workflow、requirements-definition、testing-patternsで作業パターンを定義しています。ドキュメントでは、technical-documentationとtechnical-writingで文書作成を支援しています。 #### 4.4.3. スキルの構造例: investigation-patterns {#4-dot-4-dot-3-dot-スキルの構造例-investigation-patterns} investigation-patternsスキルを例に構造を説明します。 `` でスキルの目的(コードベース調査とデバッグのための体系的パターン提供)を定義し、 `` で具体的なパターン(evidence_collection、five_whysなど)を記述します。 重要なのは `` と `` の組み合わせです。「ファイル:行番号の参照を必ず提示する」「調査結果の信頼度とカバレッジを評価する」といったベストプラクティスと、「証拠が不十分なまま推測しない」といったアンチパターンを明示することで、LLMの行動を両面から制約しています。 以下は実際のプロンプト構造です(簡略版): ```xml Provide systematic patterns for codebase investigation and debugging, ensuring evidence-based analysis with proper confidence assessment. Collect evidence systematically using appropriate tools find_symbol: Locate specific symbols by name get_symbols_overview: Understand file structure find_referencing_symbols: Trace dependencies search_for_pattern: Find patterns across codebase Ask "why" repeatedly to drill to root cause Why did the server crash? - Out of memory Why out of memory? - Connection pool exhausted Why exhausted? - Connections not being released Why not released? - Exception bypasses cleanup Root cause: Missing try-finally for connection release Standards for collecting and reporting evidence Citation: Always provide file:line references (path/to/file.ext:line_number) Confidence levels: - 90-100: Direct code evidence, explicit documentation - 70-89: Strong inference from multiple sources - 50-69: Reasonable inference with some gaps - 0-49: Speculation, insufficient evidence Guessing or making claims when evidence is insufficient Clearly state confidence levels and information gaps Making claims without file:line references Always provide file:line citations using format path/to/file.ext:line_number Always provide file:line references for all findings Rate confidence and coverage metrics for all investigation results Use Serena symbol tools before reading entire files Document information gaps and unclear points ``` ポイントは `` と `` で「何をするか」を定義し、 `` と `` で「どのように振る舞うか」を制約している点です。特に `` と `` の組み合わせが便利で、「これはダメ」だけじゃなく「代わりにこうして」まで教えてあげられます。 マルチエージェント構成の全体像を理解したところで、次章ではこのシステムをさらに強化するMCP Serverについて解説します。 ## 5. MCPによる機能拡張 {#5-dot-mcpによる機能拡張} [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) はAnthropicが策定したオープンプロトコルで、AIモデルと外部ツール・データソースを接続するための標準仕様です。Claude Codeは複数のMCP Serverを同時に利用でき、これにより機能を大幅に拡張できます。 ### 5.1. なぜ3つのMCPサーバなのか {#5-dot-1-dot-なぜ3つのmcpサーバなのか} 私は [Context7](https://github.com/upstash/context7)、[Codex](https://github.com/openai/codex)、[Serena](https://github.com/oraios/serena) という3つのMCPサーバを組み合わせています。Context7とSerenaはMCP Server、CodexはMCPクライアントとして機能し、それぞれが異なるギャップを埋めています。どれか1つが欠けても開発体験が損なわれます。 #### 5.1.1. 各サーバが埋めるギャップ {#5-dot-1-dot-1-dot-各サーバが埋めるギャップ} LLMの訓練データには期限があります。最新のReact 19のAPIや、先月リリースされたライブラリのドキュメントをClaude Codeは知りません。この問題を解決するのが[Upstash社](https://upstash.com/)が提供する **Context7** です。npmやGitHubから最新のドキュメントをリアルタイムで取得し、常に最新の情報に基づいたコード生成を可能にします。 プロジェクトには固有のパターンや規約があります。これを毎回説明するのは非効率です。[Oraios社](https://github.com/oraios)が開発した **Serena** は、シンボルレベルのコード操作と永続的なメモリ機能を提供します。LSPを通じて30以上の言語をサポートしており、一度覚えたプロジェクトの規約を次回以降も記憶してくれます。 Claude Code自身もコードを生成できますが、複雑なリファクタリングやボイラープレート生成では専用ツールが欲しくなることがあります。[OpenAI社](https://openai.com)が提供する **Codex** は[MCPクライアントとして他のMCP Serverと連携](https://developers.openai.com/codex/mcp/)でき、コード生成に特化したエンジンとして活用できます。 #### 5.1.2. ツール優先度の階層 {#5-dot-1-dot-2-dot-ツール優先度の階層} 私のワークフローでは、ツールの使用に明確な優先度を設けています。 {{< figure src="/images/948E82A2-58AB-41CB-AD87-1DF1081C5C17.png" >}} この優先度には理由があります。Priority 1のBasic Tools(Read/Edit/Write)はもっとも軽量で、単純なファイル操作には十分です。Priority 2のSerenaは、シンボル単位の操作でトークンを節約しながらコードを探索・編集できます。Priority 3のContext7は、外部ライブラリの最新ドキュメントが必要な場合にのみ使用します。Priority 4のCodexは、複雑なコード生成タスクに限定して使用し、調査や分析には使いません。 #### 5.1.3. Nix設定例 {#5-dot-1-dot-3-dot-nix設定例} [mcp-servers-nix](https://github.com/natsukium/mcp-servers-nix)を使用した宣言的なMCP Server設定を紹介します([実際の設定参照](https://github.com/takeokunn/nixos-configuration/blob/af95701a89e8128ba000dee3062eefa33c7d2001/home-manager/programs/claude-code/default.nix#L131-L142))。 ```nix mcpServers = (mcp-servers-nix.lib.evalModule pkgs { programs = { context7.enable = true; codex.enable = true; serena = { enable = true; context = "claude-code"; enableWebDashboard = false; }; }; }).config.settings.servers; ``` `context = "claude-code"` はSerenaの動作モードを指定するパラメータで、Claude Codeとの統合に最適化された設定が適用されます。 ### 5.2. 統合ワークフロー {#5-dot-2-dot-統合ワークフロー} 3つのMCP Serverは独立して動くのではなく、ワークフローの各フェーズで連携します。次のシーケンス図は、ユーザーからの実装依頼が完了するまでの流れを示しています。 {{< figure src="/images/67C913D9-DA99-4696-994B-AAE6FD9E486A.png" >}} #### 5.2.1. Phase 1: 外部知識取得(Context7) {#5-dot-2-dot-1-dot-phase-1-外部知識取得-context7} まずContext7で外部ライブラリの最新ドキュメントを取得します。 `resolve-library-id` でパッケージ名からライブラリIDを解決し、 `get-library-docs` でドキュメントを取得します。 `topic` パラメータで関心領域を絞り込むこともできます。信頼スコア7-10のライブラリを優先することで、信頼性の高いドキュメントを参照できます。 #### 5.2.2. Phase 2: 内部知識取得(Serena) {#5-dot-2-dot-2-dot-phase-2-内部知識取得-serena} 次にSerenaでプロジェクト固有のパターンを取得します。 `find_symbol` で関連するコード要素を探索し、 `read_memory` でプロジェクトの規約やベストプラクティスを読み込みます。 `find_referencing_symbols` で影響範囲を事前確認することで、安全にコードを変更できます。 Serenaのツールは4カテゴリに分かれます。シンボル検索では `get_symbols_overview` / `find_symbol` / `find_referencing_symbols` でコード要素を探索します。パターン検索では `search_for_pattern` で正規表現による横断検索をします。コード編集では `replace_symbol_body` / `insert_before_symbol` / `insert_after_symbol` / `rename_symbol` でシンボル単位を編集します。メモリでは `list_memories` / `read_memory` / `write_memory` / `edit_memory` / `delete_memory` で知識を永続化します。 #### 5.2.3. Phase 3: 実装(Codex) {#5-dot-2-dot-3-dot-phase-3-実装-codex} 外部知識と内部知識が揃ったら、Codexでコードを生成します。 `codex()` で会話を開始し、 `codex-reply()` で継続します。適切な使用場面は、新規ファイル・関数の生成、複雑なリファクタリング、ボイラープレートコードの生成です。 経験的に、Codexはコード生成が得意です。Claude Codeも優れたコード生成能力を持っていますが、Codexに任せたほうがきれいなコードが出てくることが多いと感じています。特にボイラープレートや定型的なコード生成では、その差が顕著です。 重要な制約として、調査・分析にはCodexを使わず、1回の呼び出しで1つの明確なタスクに限定し、マルチファイル編集は避けるようにしています。 #### 5.2.4. Phase 4: 知識永続化(Serena) {#5-dot-2-dot-4-dot-phase-4-知識永続化-serena} 実装完了後、新たに発見したパターンや規約を `write_memory` でSerenaに保存します。次回以降のセッションでも同じ知識を再利用できるため、プロジェクトの規約を毎回説明する手間がなくなります。 この4フェーズの連携により、最新のベストプラクティスを参照しつつ、プロジェクト固有の規約に準拠したコードを生成し、学習した知識を蓄積していけます。 MCP Serverによる機能拡張を理解したところで、次章ではhome-managerによる宣言的管理と安全性の確保について解説します。 ## 6. 運用と安全性 {#6-dot-運用と安全性} 本章では、Claude Code設定の宣言的管理と、安全性を担保するための仕組みについて解説します。 ### 6.1. home-managerによる宣言的管理 {#6-dot-1-dot-home-managerによる宣言的管理} Claude Codeの設定はhome-managerで宣言的に管理しています。 `programs.claude-code` モジュールを中心に、CLAUDE.md、agents/、commands/、skills/、hooks/、scripts/ というディレクトリ構成で整理しています。 ```nix programs.claude-code = { enable = true; package = nodePkgs."@anthropic-ai/claude-code"; memory.source = ./CLAUDE.md; settings = { theme = "dark"; autoUpdates = false; autoCompactEnabled = true; enableAllProjectMcpServers = true; outputStyle = "Explanatory"; }; agents = { ... }; commands = { ... }; skills = { ... }; hooks = { ... }; mcpServers = { ... }; }; ``` agents/commands/skills/hooksの各項目は `builtins.readFile` でMarkdownファイルを読み込む形式を採用しています。たとえば `agents = { code-quality = builtins.readFile ./agents/code-quality.md; }` のように記述します。プロンプトの内容をMarkdownで記述できるため可読性が高く、Nixの評価時に文字列として埋め込まれるためファイル単位でのdiffも取りやすくなります。 この宣言的アプローチの最大の利点は再現性です。設定をGitで管理し、 `home-manager switch` 一発で同一の環境を任意のマシンに展開できます。 ### 6.2. hooksによるポリシー強制 {#6-dot-2-dot-hooksによるポリシー強制} hooksはClaude Codeのツール実行ライフサイクルに介入する仕組みです。 私は2種類のhooksを設定しています。タスク完了通知とコマンドバリデーションです。 #### 6.2.1. hooksの種類 {#6-dot-2-dot-1-dot-hooksの種類} [公式ドキュメント](https://code.claude.com/docs/en/hooks)では10種類のhookイベントが定義されていますが、私が使用しているのは4種類です。 `PreToolUse` はツール実行前に発火し、バリデーションやコマンド検証に使用します。 `PostToolUse` はツール実行後に発火し、ログ記録や後処理に使用します。 `Stop` はセッション終了時に発火し、通知やクリーンアップに使用します。 `Notification` は通知イベントで、外部連携に使用します。 hookスクリプトの終了コード規約として、 `exit 0` は処理を許可し、 `exit 2` は処理をブロックします(stderrがエラーメッセージとして表示されます)。 #### 6.2.2. enforce-perl hook: コマンドバリデーション {#6-dot-2-dot-2-dot-enforce-perl-hook-コマンドバリデーション} CLAUDE.mdで定義した「perlでテキスト処理」ルールを強制するバリデーションhookを紹介します([hooks/enforce-perl.sh](https://github.com/takeokunn/nixos-configuration/blob/af95701a89e8128ba000dee3062eefa33c7d2001/home-manager/programs/claude-code/hooks/enforce-perl.sh))。 ```bash #!/bin/bash set -euo pipefail input=$(cat) tool_name=$(echo "$input" | jq -r '.tool_name // ""') command=$(echo "$input" | jq -r '.tool_input.command // ""') if [[ $tool_name != "Bash" ]] || [[ -z $command ]]; then exit 0 fi # sed/awk使用を検出してブロック if echo "$command" | grep -qE '\b(sed|awk)\b'; then cat >&2 <<'EOF' ❌ sed/awk detected - Use perl instead Examples: ❌ sed 's/foo/bar/g' file.txt ✅ perl -pe 's/foo/bar/g' file.txt EOF exit 2 # Block the command fi exit 0 # Allow ``` `\b(sed|awk)\b` の正規表現でワードバウンダリを指定し、 `sediment` のような誤検出を防いでいます。「perlを使え」とCLAUDE.mdに書いてあるのにsedを使ってきたら、hookがブロックして「perlにしてね」と返してくれます。 ### 6.3. permissions と statusline {#6-dot-3-dot-permissions-と-statusline} #### 6.3.1. permissions: 多層防御 {#6-dot-3-dot-1-dot-permissions-多層防御} permissionsは、Claude Codeが実行できるコマンドを制限するセーフティネットです。Defense in Depth(多層防御)の考え方で、サンドボックス(第1層)、permissions deny(第2層)、hooks validation(第3層)の3層構造で保護しています。 危険なコマンドを6つのカテゴリに分類して禁止しています。ファイル破壊( `rm -rf /` など)、システムコマンド( `shutdown` / `reboot` )、ディスク操作( `dd` / `mkfs` )です。さらにプロセス制御( `killall` / `pkill` )、ネットワークリスナー( `nc -l` )、権限昇格( `sudo rm` / `chmod 777` )も禁止しています。 #### 6.3.2. statusline: セッション状態の可視化 {#6-dot-3-dot-2-dot-statusline-セッション状態の可視化} statuslineはClaude Codeのセッション状態をリアルタイムで可視化する機能です([scripts/statusline.sh](https://github.com/takeokunn/nixos-configuration/blob/af95701a89e8128ba000dee3062eefa33c7d2001/home-manager/programs/claude-code/scripts/statusline.sh))。モデル名、カレントディレクトリ、Gitブランチ、トークン使用量と割合を表示します。 トークン使用率に応じて色を変えることで、コンパクション発生前に警告を出せます。70%未満は緑(十分な余裕あり)、70-89%は黄色(注意が必要)、90%以上は赤(コンパクション間近)で表示されます。 ここまでの章で、プロンプト設計、マルチエージェント構成、MCPによる機能拡張、そして運用と安全性について解説してきました。次章では、運用して得られた結果と所感を共有します。 ## 7. 得られた結果・所感 {#7-dot-得られた結果-所感} このワークフローを使い始めて、開発体験が明らかに変わりました。 一番大きいのは、Claude Codeの振る舞いが予測可能になったことです。以前は「今日はどんな形式で返ってくるかな」と毎回ドキドキしていましたが、XMLで構造化したプロンプトと専門エージェントへの委譲により、期待とおりの出力が得られるようになりました。 セキュリティレビューをお願いすれば、ちゃんとセキュリティの観点だけでレビューしてくれます。コードスタイルの指摘が混在することもなくなりました。これが地味に嬉しい。 `/define` → `/execute` → `/feedback` というフローも気に入っています。調査と実装が明確に分離されているので、「調べてたらいつの間にかファイルが変わってた」という事故がなくなりました。安心して「とことん調べて」といえるのは大きい。 一方で、Serenaが本当に必要なのかは正直まだわかりません。シンボルレベルの操作でトークン効率が上がるとは書きましたが、Claude Code標準のGlob/Grep/Readでも十分な気がしています。LSP設定やオンボーディングの手間を考えると、その複雑さに見合うリターンがあるのか。今後の運用で見極めていきたいところです。 ## 8. おわりに {#8-dot-おわりに} 私のClaude Codeワークフローを紹介しました。 「セキュリティレビューなのにコードスタイルの指摘ばかり」という不満から始まった試行錯誤が、気づけばマルチエージェントシステムになっていました。ここまで作り込むとは思っていませんでしたが、AIツールの設定をソフトウェアプロジェクトのように育てていくのは楽しいものです。 やりたいことはまだまだあります。プロンプト入力の英語統一、Context7とSerenaの使い分け基準の明確化、クロスプロジェクトでの知識共有など。 本記事で紹介した設定は[GitHubリポジトリ](https://github.com/takeokunn/nixos-configuration/tree/af95701a89e8128ba000dee3062eefa33c7d2001/home-manager/programs/claude-code)で公開しています。Nixユーザーの方はそのまま参考にできるはずです。[公式ドキュメント](https://code.claude.com/docs/en/overview)と合わせてどうぞ。 -------------------------------------------------------------------------------- title: "「The Rust Programming Language 日本語版」の読書記録" date: "2025-12-25" -------------------------------------------------------------------------------- ## 文献情報 {#文献情報} - 出典: [The Rust Programming Language 日本語版](https://doc.rust-jp.rs/book-ja/title-page.html) - 著者: Rust日本語ドキュメント翻訳プロジェクト - 参照日: 2025-12-25 - 種別: オンライン書籍 ## 読む目的・背景 {#読む目的-背景} 社内でRustを使っているため、業務で必要な知識として勉強する必要があった。 他言語で得た知識で知ったかぶりせず、Rust固有の概念を正確に理解したい。 特に所有権・借用・ライフタイム・スマートポインタといった概念の本質を理解することを目標とした。 ## 重要ポイントと引用 {#重要ポイントと引用} ### 所有権(Ownership) {#所有権-ownership} > Rustの各値は、所有者と呼ばれる変数と対応している。いかなる時も所有者は一つである。所有者がスコープから外れたら、値は破棄される。 > > --- [第4章 所有権を理解する](https://doc.rust-jp.rs/book-ja/ch04-01-what-is-ownership.html) 所有権はRustのもっとも特徴的な機能であり、ガベージコレクタなしでメモリ安全性を保証する仕組みになっている。 C/C++ではプログラマが手動でメモリを管理する必要があり、解放忘れ(メモリリーク)や二重解放といったバグが発生しやすかった。 一方、JavaやPythonなどのGC言語では実行時のオーバーヘッドが発生する。 Rustは所有権システムによって、コンパイル時にメモリ管理の正しさを検証し、実行時コストなしで安全性を実現している。 所有権には3つのルールがある。 1. Rustの各値は所有者と呼ばれる変数をもつ 2. いかなる時も所有者は1つだけ 3. 所有者がスコープから外れると、値は破棄される( `drop` が呼ばれる)。 ```rust fn main() { let s1 = String::from("hello"); // s1が所有者 let s2 = s1; // 所有権がs2にムーブ // println!("{}", s1); // エラー: s1は無効 println!("{}", s2); // OK: s2が所有者 } ``` ヒープに確保されるデータ( `String` など)は代入時に所有権がムーブする。 スタックに確保される固定サイズのデータ(整数など)は `Copy` トレイトを実装しており、コピーされる。 ### 借用(Borrowing) {#借用-borrowing} > 関数の引数に参照を取ることを借用と呼びます。現実生活のように、誰かが何かを所有していたら、それを借りることができます。用が済んだら、返さなきゃいけないわけです。 > > --- [第4章 参照と借用](https://doc.rust-jp.rs/book-ja/ch04-02-references-and-borrowing.html) 借用は所有権をムーブせずにデータにアクセスする仕組みになっている。 関数に値を渡すたびに所有権が移動すると、呼び出し元で値が使えなくなり不便になる。 借用を使えば、所有権を保持したまま一時的にデータへのアクセスを許可できる。 借用には2種類ある。 不変参照( `&T` )は読み取り専用で、同時に複数存在できる。 可変参照( `&mut T` )は書き込み可能だが、同時に1つしか存在できない。 この制約により、データ競合をコンパイル時に防止する。 ```rust fn main() { let mut s = String::from("hello"); // 不変参照: 複数OK let r1 = &s; let r2 = &s; println!("{}, {}", r1, r2); // 可変参照: 1つだけ let r3 = &mut s; r3.push_str(" world"); println!("{}", r3); } ``` 重要なルールとして、不変参照が存在する間は可変参照を作成できない。 これはRustの「共有XOR可変」原則と呼ばれ、複数の読み取りか単一の書き込みのどちらかしか許可しない。 ### ライフタイム(Lifetime) {#ライフタイム-lifetime} > ライフタイムの主な目的は、ダングリング参照を回避することです > > --- [第10章 ライフタイムで参照を検証する](https://doc.rust-jp.rs/book-ja/ch10-03-lifetime-syntax.html) ライフタイムは参照が有効な期間を表すアノテーションとなっている。 Rustコンパイラの借用チェッカーは、すべての参照がそのライフタイム内で有効であることを検証する。 これによりダングリング参照(無効なメモリを指す参照)をコンパイル時に防止する。 多くの場合、ライフタイムは暗黙的に推論される(ライフタイム省略規則)。 しかし、複数の参照を受け取って参照を返す関数では、コンパイラがどの入力参照と出力参照が関連しているかを判断できないため、明示的なライフタイムアノテーションが必要になる。 ```rust // 'a はライフタイムパラメータ // 戻り値の参照は、x と y のうち短い方のライフタイムを持つ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let s1 = String::from("long string"); let result; { let s2 = String::from("short"); result = longest(&s1, &s2); println!("{}", result); // OK: s2がまだ有効 } // println!("{}", result); // エラー: s2のライフタイムが終了 } ``` ライフタイムアノテーション `'a` は参照の実際の生存期間を変更しない。 複数の参照間の関係をコンパイラに伝えるためのものとなっている。 構造体が参照を保持する場合も、ライフタイムアノテーションが必要になる。 ### スマートポインタ(Smart Pointer) {#スマートポインタ-smart-pointer} > スマートポインタは、ポインタのように振る舞うだけでなく、追加のメタデータと能力があるデータ構造です > > --- [第15章 スマートポインタ](https://doc.rust-jp.rs/book-ja/ch15-00-smart-pointers.html) スマートポインタはポインタのように振る舞いつつ、メモリ管理などの追加機能をもつデータ構造となっている。 通常の参照は単にデータを借用するだけだが、スマートポインタはデータを所有することが多い。 代表的なスマートポインタには `Box` 、 `Rc` 、 `RefCell` がある。 `Box` はヒープにデータを確保するもっともシンプルなスマートポインタとなっている。 再帰的なデータ構造(リンクリストなど)やサイズが不明な型を扱う際に使用する。 ```rust // 再帰的なデータ構造にはBoxが必要 enum List { Cons(i32, Box), Nil, } fn main() { let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil)))); } ``` `Rc` (Reference Counting)は複数の所有者をもつデータを扱う。 参照カウントにより、最後の所有者がスコープを抜けた時点でデータが破棄される。 ただしシングルスレッド専用であり、マルチスレッドでは `Arc` を使用する。 `RefCell` は実行時に借用規則をチェックする「内部可変性」パターンを実現する。 コンパイル時には不変でも、実行時に内部の値を変更できる。 `Rc>` の組み合わせで、複数の所有者が可変データを共有できる。 ### 並行性(Concurrency) {#並行性-concurrency} > 所有権と型チェックを活用することで、多くの並行性エラーは、実行時エラーではなくコンパイル時エラーになります。Rustのこの方向性を恐れるな!並行性とニックネーム付けしました。 > > --- [第16章 恐れるな!並行性](https://doc.rust-jp.rs/book-ja/ch16-00-concurrency.html) Rustの所有権システムは並行プログラミングにおいても強力な安全性を提供する。 データ競合(複数のスレッドが同じメモリに同時にアクセスし、少なくとも1つが書き込みを行う状況)はコンパイル時に検出される。 スレッド間でデータを共有するには `Send` トレイトと `Sync` トレイトが重要になる。 `Send` はスレッド間で所有権を移動できることを示し、 `Sync` は複数スレッドからの参照が安全であることを示す。 ほとんどの型は自動的にこれらを実装するが、 `Rc` は `Send` を実装しないため、スレッド間で共有するには `Arc` (Atomic Reference Counting)を使用する。 ```rust use std::sync::{Arc, Mutex}; use std::thread; fn main() { // Arc: スレッド間で安全に共有 // Mutex: 排他的アクセスを保証 let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Result: {}", *counter.lock().unwrap()); } ``` `Mutex` は排他制御を提供し、一度に1つのスレッドのみがデータにアクセスできることを保証する。 ロックの解放忘れはRAIIパターンにより自動的に処理される。 また、メッセージパッシングによるスレッド間通信には `std::sync::mpsc` チャネルを使用する。 ### パターンとマッチング(Pattern Matching) {#パターンとマッチング-pattern-matching} > パターンは、複雑であれ、単純であれ、Rustで型の構造に一致する特別な記法です。 > > --- [第18章 パターンとマッチング](https://doc.rust-jp.rs/book-ja/ch18-00-patterns.html) パターンマッチングはRustの強力な制御フロー機能となっている。 `match` 式はすべてのケースを網羅的にチェックし、漏れがあればコンパイルエラーになる。 これにより `null` や例外による実行時エラーを防ぐ。 パターンは `match` 式だけでなく、さまざまな場所で使用できる。 `if let` や `while let` 、 `for` ループ、関数引数、 `let` 文などが対象となる。 構造体やenumの分解、参照のマッチング、ガード条件など、豊富なパターン記法がある。 ```rust enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn process_message(msg: Message) { match msg { Message::Quit => println!("終了"), Message::Move { x, y } => println!("移動: ({}, {})", x, y), Message::Write(text) => println!("メッセージ: {}", text), Message::ChangeColor(r, g, b) => { println!("色変更: RGB({}, {}, {})", r, g, b) } } } fn main() { // if let: 1つのパターンのみをマッチしたい場合 let some_value = Some(5); if let Some(x) = some_value { println!("値: {}", x); } // ガード条件 let num = Some(4); match num { Some(x) if x < 5 => println!("5未満: {}", x), Some(x) => println!("5以上: {}", x), None => println!("なし"), } } ``` Rustでは `Option` と `Result` 型を多用する。 これらのenumとパターンマッチングを組み合わせることで、null参照やエラー処理を型システムで安全に扱える。 ## 自分の考察・気づき {#自分の考察-気づき} 他言語で「なんとなく動く」と思っていたメモリ管理が、Rustでは明示的に理解する必要がある。 GCのある言語では意識しなかった所有権の概念が、C/C++のメモリ管理の問題を解決していることに気づいた。 特にスタックとヒープの違いを意識する必要がある。 スタックは固定サイズのデータを高速に確保・解放でき、ヒープは可変サイズのデータを扱えるが管理コストがかかる。 Rustではこの違いが所有権やムーブセマンティクスに直結しており、 `Copy` トレイトを実装する型(整数など)はスタックでコピーされ、 `String` などのヒープデータはムーブされる。 コンパイラが「親切に」エラーを教えてくれる設計思想が印象的だった。「ゼロコスト抽象化」という概念、つまり安全性と性能の両立を実現している点も興味深い。 ## 関連する知識へのリンク {#関連する知識へのリンク} - [社内読書会でThe Rust Programming Languageを読破した]({{< relref "../permanent/20251220235900-read_the_rust_programming_language.md" >}}) ## 今後の活用案 {#今後の活用案} JSONパーサを自作してみる。 -------------------------------------------------------------------------------- title: "blogをdev.toサポートした" date: "2025-12-25" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} [takeokunn/blog](https://github.com/takeokunn/blog) でdev.toの記事を管理できるようにしたのでメモしておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. 対応理由整理 {#1-dot-対応理由整理} [takeokunn/blog](https://github.com/takeokunn/blog) は次の3つのターゲットに対してorg-roamでコンテンツ管理をしている。 - Hugo - Repo: - Url: - Zenn - Repo: - Url: - PDF(Typst) - Repo: - Url: 以前から英語でもブログを書きたいという欲求があった。 ホスティング先はどこがよいのか検討した所、[dev.to](https://dev.to/) がなんだかんだ安牌であることがわかったので対応することにした。 ### 2. NixでBuildできるようにする {#2-dot-nixでbuildできるようにする} `flake.nix` は以下。 ```nix { packages = { build-devto = pkgs.stdenv.mkDerivation { name = "build-devto"; src = ./.; nativeBuildInputs = with pkgs; [ (emacsPkg.pkgs.withPackages (epkgs: (with epkgs.melpaPackages; [ ox-gfm ]))) ]; buildPhase = '' mkdir -p ./articles emacs --batch --load scripts/ox-devto.el --funcall export-org-devto-files ''; installPhase = '' mkdir -p $out # Article Sync Format: articles/{slug}/article.json + article.md for dir in ./articles/*/; do if [ -f "$dir/article.json" ]; then cp -r "$dir" $out/ fi done ''; }; }; } ``` `ox-devto.el` を適当に作り、 `ox-gfm` をdev.to用にwrapした。 ```emacs-lisp (org-export-define-derived-backend 'devto 'gfm :options-alist '((:devto-tags "TAGS" nil nil t) (:devto-description "DESCRIPTION" nil nil t) (:devto-id "DEVTO_ID" nil nil t) (:devto-slug "DEVTO_SLUG" nil nil t))) (defun export-org-devto-files () "Export all Org files in articles directory to Article Sync format." (setq org-publish-timestamp-directory org-devto-timestamp-directory) (let ((org-publish-project-alist `(("devto" :recursive t :base-directory ,org-devto-articles-directory :base-extension "org" :publishing-directory ,org-devto-output-directory :publishing-function org-devto-publish-to-article-sync)))) (org-publish-all t))) ``` ### 3. 実際にorg-modeで記事を書く {#3-dot-実際にorg-modeで記事を書く} `DEVTO_ID` や `DEVTO_SLUG` は実際にブログ記事を生成してからじゃないと取得できない値なので注意。 管理画面で手動で記事を作成してから入力するフローになるようだ。 ```org :PROPERTIES: :ID: 51301ebb-2373-0794-838b-44ab40369fc3 :END: #+TITLE: devto test1 aaaa #+AUTHOR: takeokunn #+DESCRIPTION: description #+TAGS: sandbox #+DEVTO_ID: 3048601 #+DEVTO_SLUG: devto-test-ol9 * testhogehogeho testfdsafsafafa #+begin_src bash echo "hello world" #+end_src * zzzzzzzzzzz #+begin_quote fdasfasfa #+end_quote ``` ### 4. GitHub Actionsで同期する {#4-dot-github-actionsで同期する} [dev.toの記事もGitHubで管理してみた - Zenn](https://zenn.dev/yuta28/articles/dev-github-vscode) を参考にGitHub Actionsを設定した。 同期してるMarkdownがどういう状況か可視化する為に `devto` ブランチを作成する運用にしてる。 ```yaml build-devto: runs-on: ubuntu-latest needs: ci defaults: run: working-directory: ./devto steps: - uses: actions/checkout@v6 - name: Setup nix uses: ./.github/actions/setup-nix with: cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - name: Build devto (Article Sync format) run: nix build .#build-devto - name: Prepare articles directory run: | mkdir -p public/ rsync -a --chmod=u+w result/ public/ - name: Sync articles to dev.to uses: calvinmclean/article-sync@v1.3.4 with: api_key: ${{ secrets.DEV_TO_TOKEN }} article_path: ./devto/public type: synchronize gh_token: ${{ secrets.GITHUB_TOKEN }} - name: Create devto branch uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./devto/public publish_branch: devto force_orphan: true ``` ## 得られた結果・所感 {#得られた結果-所感} 無事テスト記事の投稿ができた。 {{< figure src="/images/72A3B776-C01B-40B2-9F93-54A5B7D41530.png" >}} ## 今後の展開・検討事項 {#今後の展開-検討事項} 英語記事を大量に書いて英語力を上げたい。 -------------------------------------------------------------------------------- title: "Neovimの設定をNixvimに移行した" date: "2025-12-25" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 私はEmacsユーザだが、最近同僚エンジニアが全員Neovimユーザの会社に転職してしまった。 よい機会だしある程度Neovimを使えた方がいいなと思い、Neovimをひととおり設定したのでメモしておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. 管理方法選定 {#1-dot-管理方法選定} 次のニーズを満たすものを探していた。 - 普段メインで触らないので塩漬ビリティの高い状態にする - lua scriptは可能なら書きたくない、期間が開くとどうせ忘れる - AIに書かせても動くことを保証しやすい状態にする - プラグイン管理と設定が近い状態にする Nixにないかなと調査してた所、[Nixvim](https://github.com/nix-community/nixvim) を使えば上記の要件を満たせることがわかったので導入してみた。 余談だが公式ドキュメントは情報量が少ないのでソースコードを読んだ方がいい。 ### 2. 初期構築 {#2-dot-初期構築} インストールガイドなどを参考に導入する。 flake.nixで設定する。 ```nix { inputs = { nixvim = { url = "github:nix-community/nixvim"; inputs.nixpkgs.follows = "nixpkgs"; }; }; } ``` home-managerのsharedModulesで読み込む。 ```nix nix-darwin.lib.darwinSystem { modules = [ home-manager.darwinModules.home-manager { home-manager = { sharedModules = [ nixvim.homeModules.nixvim ]; }; } ]; } ``` あとはNixvimを有効にするだけ。 ```nix { programs.nixvim = { enable = true; colorschemes.catppuccin.enable = true; plugins.lualine.enable = true; }; } ``` ### 3. AIに設定させる {#3-dot-aiに設定させる} あとは好みの設定をするだけ。 この記事を書いてる時点では、次のようなディレクトリ構造で用意してる。 あまり何も考えずに「モダンなプラグインを入れて」とClaude Codeに指示して書かせてる。 ```console $ pwd /Users/take/ghq/github.com/takeokunn/nixos-configuration/home-manager/programs/nixvim $ nix run nixpkgs#tree . . |-- default.nix |-- globals | `-- default.nix |-- keymaps | |-- basic.nix | |-- default.nix | `-- window.nix |-- modules | |-- oil-git-status.nix | |-- skkeleton.nix | `-- vim-sandwich.nix |-- opts | |-- default.nix | |-- display.nix | |-- editing.nix | |-- performance.nix | `-- search.nix |-- packages | `-- default.nix `-- plugins |-- core | |-- editing.nix | `-- file-explorer.nix |-- default.nix |-- language | |-- japanese.nix | |-- lsp.nix | |-- markdown.nix | |-- rust.nix | |-- treesitter.nix | `-- web.nix |-- tools | |-- git.nix | |-- harpoon.nix | |-- misc.nix | `-- telescope.nix `-- ui |-- bufferline.nix |-- dashboard.nix |-- icons.nix |-- navigation.nix |-- notifications.nix |-- statusline.nix `-- visual-aids.nix 11 directories, 34 files ``` NeovimやLuaや事情は分からないが、 `performance.byteCompileLua` でByte Compileしてくれるみたいなので有効にできるようだ。 Neovimのプラグイン事情はよくわからないのでいったんすべてのプラグインをLua製に統一させた。 ```nix { programs.nixvim = { luaLoader.enable = true; performance.byteCompileLua = { enable = true; nvimRuntime = true; configs = true; plugins = true; }; }; } ``` ## 得られた結果・所感 {#得られた結果-所感} モダンなエディタくらいの操作感になったし、安定的に動いてくれるようになった。 既存のNixの更新サイクルにも乗るようになったので、毎日最新のプラグインが入って確実に動くNeovimを使えるフローが固まった。 {{< figure src="/images/7CC4F84F-6F57-4036-AED9-E0FAD965E3B5.png" >}} ## 今後の展開・検討事項 {#今後の展開-検討事項} Neovim環境がかなり整ったので普段の開発でもNeovimを積極的に使いたい。 -------------------------------------------------------------------------------- title: "社内読書会でThe Rust Programming Languageを読破した" date: "2025-12-25" -------------------------------------------------------------------------------- ## 1. Introduction {#1-dot-introduction} 社内でRust本の読書会を開催したのですが、大成功で終わりました。 過去に社内読書会に参加した経験から、読書会というのはうまくいかないものだと思っていました。 途中で自然消滅したり、一部の人だけが話して終わったり、結局何も身につかなかったりと、苦い経験がいくつもあります。 今回は全員満足度も理解度も高く大成功に終わったという実感があるので、何が良かったのか後学の為に纏めておきます。 あくまで私個人の経験に基づく独断と偏見が多分に含まれている点をご了承ください。 ## 2. 開催情報 {#2-dot-開催情報} ### 2.1. 基本情報 {#2-dot-1-dot-基本情報} 次の期間/時間に開催しました。 - 2025年9月〜2025年12月 - 毎日平日 17:00〜17:30 20章と分量のある[The Rust Programming Language 日本語版](https://doc.rust-jp.rs/book-ja/title-page.html#the-rust-programming-language-%E6%97%A5%E6%9C%AC%E8%AA%9E%E7%89%88)を読破しました。 メンバーは8人程度で、2人がRust熟練者、それ以外はRust初学者という構成でした。 ### 2.2. 時間設定の意図 {#2-dot-2-dot-時間設定の意図} 17:00〜17:30という時間帯は業務の終盤で集中力が切れやすい時間帯です。 読書会にあてることでリラックスできたかなと思っています。 30分という短い時間の意図はそもそもが業務時間内というのと毎日継続するのにちょうどよい長さだからでした。 長時間の読書会は準備も負担も大きくなりがちですが、30分であれば「今日はちょっと疲れているな」という日でも参加しやすかったです。 ### 2.3. 進め方 {#2-dot-3-dot-進め方} 30分間の内訳は以下です。 ドキュメントツールに開催日付で記事を作り、共同編集で学んだことをメモする形式にしていました。 - 前半20分: 前回の続きから読書、学んだことや発見や疑問を雑にメモする - 後半10分: メモを見ながら全員に共有 ## 3. 個人的な目標 {#3-dot-個人的な目標} 「落ち着いて座学をやる」というのが年々苦手になってきていた実感があったので、「ちゃんと机に座ってひとつずつ落ち着いて理解しながら進めていく」というのを目標に掲げていました。 プログラミング言語の勉強といえど、手を一切動かさず、頭だけで理論を理解することを徹底しました。 特に今回の題材は理論を丁寧に理解していく必要があるRustだったので、題材としてはうってつけでした。 また、「他言語で得た知識で知ったかぶりしない」という目標もありました。 良くも悪くも普段プログラムを書いているせいで大体の言語は手なりに書けてしまっていますし、特に最近はAIのせいで深く考えなくても良くなってしまっています。 これが成長の足枷になってしまっているなという実感があったので、今回は理解が浅い部分の理解をサボらないことにしました。 ## 4. うまくいった理由の考察 {#4-dot-うまくいった理由の考察} 主に次の3パターンの取り組みをしました。 - 元の文章を読む - Geminiで理解の浅い所を深堀する - 自分の理解した部分を有識者に語る会を開く ### 4.1. Geminiの活用 {#4-dot-1-dot-geminiの活用} 普段の読書会中は、その日に読む分をGeminiに質問しまくりながら読んでいました。 Geminiに「自分の理解ではこうだけどあってる?」のようにとにかく聞いて正確な理解に努めました。 一般的な知識なのでハルシネーションがほぼなかったです。 具体的には次のような質問を投げていました。 - 「このコードがコンパイルエラーになる理由を、ライフタイムの観点から説明して」 - 「自分の理解ではこうだけど合ってる?」という確認 疑問点をすぐに解消できたのが助かりました。 ### 4.2. 有識者に語る会 {#4-dot-2-dot-有識者に語る会} とはいえ、Rustは「所有権」や「借用」や「ライフタイム」や「スマートポインタ」など理解しにくい概念がいくつもあります。 そこで、必要に応じて「自分の理解した部分を有識者に語る会」を30分設けてもらいました。有識者にひたすら自分の理解を喋りまくるという形式です。 「有識者に語る」というのが非常に重要でした。 言葉を適当に濁せないし、用語を適当に使えないし、ちゃんと準備をしないといけないのが体験としてよかったです。 Geminiに質問攻めして「何が説明できれば俺は理解してるといえると思う?」とプロンプトに投げまくってました。 有識者に説明すると新しい知識が出てきたり、自分の中でしっくりする理解ができたのが良かったです。 世間的にはファインマンテクニックと呼ばれる手法です。 [人生を変える最強学習メソッド、ファインマン・テクニック - Qiita](https://qiita.com/KanNishida/items/e2e926bb194fd280c6d9) ### 4.3. チーム構成と継続性 {#4-dot-3-dot-チーム構成と継続性} 自分以外にもRust初心者が多かったので、他の人の質問から派生した知識も理解できました。読書会はチーム構成が重要だなと思いました。 有識者は必ず1人は必要ですね。 「毎日必ず集まって読む」というのもよい取り組みでした。 日によっては眠かったりやる気がない日もありましたが、時間を固定化することによってコツコツ進められました。 現代社会に自分を最適化すべく「要点だけをうまく抽出して速習をしなくちゃいけない」ものだと思い込んでいた節があったので反省させられました。 ## 5. 今後やりたいこと {#5-dot-今後やりたいこと} この3ヶ月で座学レベルでそれなりにRustを理解できたはずですが、ほぼプログラムを手元のエディタで書かずに終わってしまいました。 プログラムは動かしてなんぼなので、まずはJSONパーサを書いてみたいと考えています。 JSONパーサは練習題材としてちょうど良さそうですね。 他にも、家に積読が山のようにあるのでゆっくりコツコツ読んでいきたいです。 理解した内容を文章に纏めておき、必要に応じて説明できるようにしておくことは年々忘れっぽくなってる今こそ必要なことのように感じています。 ZettelkastenでいうところのLiterature Notesを積極的に書いていきたいです。 ## Backlinks {#backlinks} - [「The Rust Programming Language 日本語版」の読書記録]({{< relref "../literature/TheRustP1-online.md" >}}) -------------------------------------------------------------------------------- title: "複数人でAirbnbを借りた時の清算方法" date: "2025-12-25" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 毎年VimConfでは秋葉原付近に2泊3日でAirbnbを借りて参加している。 10人くらい入れる物件を借りて、複数人が色んなパターンで遊びに来る。 きれいに清算をしたい時のTipsをメモしておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. 条件を整理する {#1-dot-条件を整理する} 割り勘をするにあたって、ステータスに応じて重みづけしたい。 学生には多く払わせる訳にはいかないし、途中で帰ったりする人にもある程度支払ってもらいたいもの。 とはいえ、あまり複雑にしてもややこしくなるので、次の2軸で重みつけして清算することにした。 - 宿泊数 - 基本はコアメンバーで割り勘 - 一泊したら1、途中で帰ったら0.5みたいに雑に計算する - 役割 - 学生は0倍、新卒は0.5倍、多く出していい人は1.5〜2倍などを用意する ### 2. 清算シートを作る {#2-dot-清算シートを作る} 次のように清算シートを作った。 人ごとに重みつけをして支払い金額を按分し、100円単位で丸める。 あとはslack上で専用チャンネルを作り、支払い先を用意して払ってもらい、シート上で支払い状況を可視化して、未払の人に対してリマインドしやすい形を作る。 {{< figure src="/images/A07647FD-EDD8-42C4-9665-E8DAD086833E.png" >}} ## 得られた結果・所感 {#得られた結果-所感} スプレッドシートを共有して納得感があるか確認し、支払い状況をシート上で可視化したらスムーズに清算できるようになった。 「学生に払わせる訳にはいかない」という個人的なポリシーがあるので、自分が清算業務をしてそもそも多く払うことで解決することにしてる。 細かいコンビニでの買い物は面倒なので、その場にいる人で都度清算かポケットマネーで出すようにしてる。 清算業務でもっとも重要なのは「ややこしさを減らす」「困ったら自分の金で解決」なので、ケチケチしない方が早く終わる。 ## 今後の展開・検討事項 {#今後の展開-検討事項} この考え方を使えば他のユースケースでも使えるのであらゆる所で使いたい。 -------------------------------------------------------------------------------- title: "digツールはdoggoが便利" date: "2025-12-24" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} インフラ構築する時にdigを叩くことは必須なのだが、2025年にdigコマンドをそのまま使うのは流石に辛い。 もう少しリッチなものがないか調べた所、doggoが良さそうだったので導入してみた。 ## 試したこと・やったこと {#試したこと-やったこと} ### 0. ツール選定 {#0-dot-ツール選定} Rust製のツールに [dog](https://github.com/ogham/dog) というものがあるが、最終更新が2021年前と期間が空いてる。 後継にあたるツールがないかなと思って調査した所、Golang製の [doggo](https://github.com/mr-karan/doggo) というツールがあった。 > It's totally inspired by dog which is written in Rust. I wanted to add some features to it but since I don't know Rust, I found it as a nice opportunity to experiment with writing a DNS Client from scratch in Go myself. Hence the name dog + go => doggo. Nixでパッケージングされているし、CLIとしての完成度も高く、アクティブなプロジェクトだったので導入した。 ### 1. home-manager module用意 {#1-dot-home-manager-module用意} 特に設定項目がないのでshell completionだけ有効にしている。 ```nix { pkgs, lib, config, ... }: let cfg = config.programs.doggo; in with lib; { options.programs.doggo = { enable = mkEnableOption "Command-line DNS Client for Humans. Written in Golang"; package = mkPackageOption pkgs "doggo" { }; enableBashIntegration = lib.hm.shell.mkBashIntegrationOption { inherit config; }; enableZshIntegration = lib.hm.shell.mkZshIntegrationOption { inherit config; }; enableFishIntegration = lib.hm.shell.mkFishIntegrationOption { inherit config; }; }; config = mkIf cfg.enable { home.packages = [ cfg.package ]; programs = { bash.initExtra = mkIf (cfg.enableBashIntegration && cfg.package != null) '' eval "$(${getExe cfg.package} completions bash)" ''; zsh.initContent = mkIf (cfg.enableZshIntegration && cfg.package != null) '' eval "$(${getExe cfg.package} completions zsh)" ''; fish.interactiveShellInit = mkIf (cfg.enableFishIntegration && cfg.package != null) '' ${getExe cfg.package} completions fish | source ''; }; }; } ``` あとは `programs.doggo.enable = true;` で有効にするだけで使えるようになる。 ### 2. 実際に使ってみる {#2-dot-実際に使ってみる} helpを引くとquicやtlsにも対応してることが分かる。 ```console NAME: doggo üê∂ DNS Client for Humans USAGE: doggo [--] [query options] [arguments...] VERSION: v1.1.2 - unknown EXAMPLES: doggo mrkaran.dev Query a domain using defaults. doggo mrkaran.dev CNAME Query for a CNAME record. doggo mrkaran.dev MX @9.9.9.9 Uses a custom DNS resolver. doggo -q mrkaran.dev -t MX -n 1.1.1.1 Using named arguments. doggo mrkaran.dev --aa --ad Query with Authoritative Answer and Authenticated Data flags set. doggo mrkaran.dev --cd --do Query with Checking Disabled and DNSSEC OK flags set. doggo mrkaran.dev --gp-from Germany Query using Globalping API from a specific location. FREE FORM ARGUMENTS: Supply hostnames, query types, and classes without flags. Example: doggo mrkaran.dev A @1.1.1.1 TRANSPORT OPTIONS: Specify the protocol with a URL-type scheme. UDP is used if no scheme is specified. @udp:// eg: @1.1.1.1 initiates a UDP query to 1.1.1.1:53. @tcp:// eg: @tcp://1.1.1.1 initiates a TCP query to 1.1.1.1:53. @https:// eg: @https://cloudflare-dns.com/dns-query initiates a DOH query to Cloudflare via DoH. @tls:// eg: @tls://1.1.1.1 initiates a DoT query to 1.1.1.1:853. @sdns:// initiates a DNSCrypt or DoH query using a DNS stamp. @quic:// initiates a DOQ query. SUBCOMMANDS: completions [bash|zsh|fish] Generate the shell completion script for the specified shell. QUERY OPTIONS: -q, --query=HOSTNAME Hostname to query the DNS records for (eg mrkaran.dev). -t, --type=TYPE Type of the DNS Record (A, MX, NS etc). -n, --nameserver=ADDR Address of a specific nameserver to send queries to (9.9.9.9, 8.8.8.8 etc). -c, --class=CLASS Network class of the DNS record (IN, CH, HS etc). -x, --reverse Performs a DNS Lookup for an IPv4 or IPv6 address. Sets the query type and class to PTR and IN respectively. --any Query all supported DNS record types (A, AAAA, CNAME, MX, NS, PTR, SOA, SRV, TXT, CAA). RESOLVER OPTIONS: --strategy=STRATEGY Specify strategy to query nameserver listed in etc/resolv.conf. (all, random, first). --ndots=INT Specify ndots parameter. Takes value from /etc/resolv.conf if using the system namesever or 1 otherwise. --search Use the search list defined in resolv.conf. Defaults to true. Set --search=false to disable search list. --timeout=DURATION Specify timeout for the resolver to return a response (e.g., 5s, 400ms, 1m). -4, --ipv4 Use IPv4 only. -6, --ipv6 Use IPv6 only. --tls-hostname=HOSTNAME Provide a hostname for verification of the certificate if the provided DoT nameserver is an IP. --skip-hostname-verification Skip TLS Hostname Verification in case of DOT Lookups. QUERY FLAGS: --aa Set Authoritative Answer flag. --ad Set Authenticated Data flag. --cd Set Checking Disabled flag. --rd Set Recursion Desired flag (default: true). --z Set Z flag (reserved for future use). --do Set DNSSEC OK flag. EDNS OPTIONS: --nsid Request Name Server Identifier (NSID) to identify the nameserver. --cookie Request DNS Cookie for enhanced security and protection against spoofing. --padding Request EDNS padding for privacy. Helps mitigate traffic analysis attacks. --ede Request Extended DNS Errors for detailed error information. --ecs=SUBNET EDNS Client Subnet (e.g., '192.0.2.0/24' or '2001:db8::/32'). Send client subnet for geo-aware responses. OUTPUT OPTIONS: -J, --json Format the output as JSON. --short Short output format. Shows only the response section. --color Defaults to true. Set --color=false to disable colored output. --debug Enable debug logging. --time Shows how long the response took from the server. GLOBALPING OPTIONS: --gp-from=Germany Query using Globalping API from a specific location. --gp-limit=INT Limit the number of probes to use from Globalping. ``` 雑に `takeokunn.org` のNSを引くとこんな感じで出力される。 ```console $ doggo takeokunn.org NS NAME TYPE CLASS TTL ADDRESS NAMESERVER takeokunn.org. NS IN 3600s bob.ns.cloudflare.com. 192.168.2.1:53 takeokunn.org. NS IN 3600s mina.ns.cloudflare.com. 192.168.2.1:53 bob.ns.cloudflare.com. A IN 3041s 108.162.193.104 192.168.2.1:53 bob.ns.cloudflare.com. A IN 3041s 172.64.33.104 192.168.2.1:53 bob.ns.cloudflare.com. A IN 3041s 173.245.59.104 192.168.2.1:53 mina.ns.cloudflare.com. A IN 3366s 108.162.194.251 192.168.2.1:53 mina.ns.cloudflare.com. A IN 3366s 162.159.38.251 192.168.2.1:53 mina.ns.cloudflare.com. A IN 3366s 172.64.34.251 192.168.2.1:53 bob.ns.cloudflare.com. AAAA IN 3041s 2606:4700:58::adf5:3b68 192.168.2.1:53 bob.ns.cloudflare.com. AAAA IN 3041s 2803:f800:50::6ca2:c168 192.168.2.1:53 bob.ns.cloudflare.com. AAAA IN 3041s 2a06:98c1:50::ac40:2168 192.168.2.1:53 mina.ns.cloudflare.com. AAAA IN 3366s 2606:4700:50::a29f:26fb 192.168.2.1:53 mina.ns.cloudflare.com. AAAA IN 3366s 2803:f800:50::6ca2:c2fb 192.168.2.1:53 mina.ns.cloudflare.com. AAAA IN 3366s 2a06:98c1:50::ac40:22fb 192.168.2.1:53 takeokunn.org. NS IN 3600s bob.ns.cloudflare.com. 192.168.2.1:53 takeokunn.org. NS IN 3600s mina.ns.cloudflare.com. 192.168.2.1:53 bob.ns.cloudflare.com. A IN 3041s 108.162.193.104 192.168.2.1:53 bob.ns.cloudflare.com. A IN 3041s 172.64.33.104 192.168.2.1:53 bob.ns.cloudflare.com. A IN 3041s 173.245.59.104 192.168.2.1:53 mina.ns.cloudflare.com. A IN 3366s 108.162.194.251 192.168.2.1:53 mina.ns.cloudflare.com. A IN 3366s 162.159.38.251 192.168.2.1:53 mina.ns.cloudflare.com. A IN 3366s 172.64.34.251 192.168.2.1:53 bob.ns.cloudflare.com. AAAA IN 3041s 2606:4700:58::adf5:3b68 192.168.2.1:53 bob.ns.cloudflare.com. AAAA IN 3041s 2803:f800:50::6ca2:c168 192.168.2.1:53 bob.ns.cloudflare.com. AAAA IN 3041s 2a06:98c1:50::ac40:2168 192.168.2.1:53 mina.ns.cloudflare.com. AAAA IN 3366s 2606:4700:50::a29f:26fb 192.168.2.1:53 mina.ns.cloudflare.com. AAAA IN 3366s 2803:f800:50::6ca2:c2fb 192.168.2.1:53 mina.ns.cloudflare.com. AAAA IN 3366s 2a06:98c1:50::ac40:22fb 192.168.2.1:53 takeokunn.org. NS IN 3600s mina.ns.cloudflare.com. [2408:210:102:4b00:32be:3bff:fe16:7102]:53 takeokunn.org. NS IN 3600s bob.ns.cloudflare.com. [2408:210:102:4b00:32be:3bff:fe16:7102]:53 bob.ns.cloudflare.com. A IN 3041s 108.162.193.104 [2408:210:102:4b00:32be:3bff:fe16:7102]:53 bob.ns.cloudflare.com. A IN 3041s 172.64.33.104 [2408:210:102:4b00:32be:3bff:fe16:7102]:53 bob.ns.cloudflare.com. A IN 3041s 173.245.59.104 [2408:210:102:4b00:32be:3bff:fe16:7102]:53 mina.ns.cloudflare.com. A IN 3366s 108.162.194.251 [2408:210:102:4b00:32be:3bff:fe16:7102]:53 mina.ns.cloudflare.com. A IN 3366s 162.159.38.251 [2408:210:102:4b00:32be:3bff:fe16:7102]:53 mina.ns.cloudflare.com. A IN 3366s 172.64.34.251 [2408:210:102:4b00:32be:3bff:fe16:7102]:53 bob.ns.cloudflare.com. AAAA IN 3041s 2606:4700:58::adf5:3b68 [2408:210:102:4b00:32be:3bff:fe16:7102]:53 bob.ns.cloudflare.com. AAAA IN 3041s 2803:f800:50::6ca2:c168 [2408:210:102:4b00:32be:3bff:fe16:7102]:53 bob.ns.cloudflare.com. AAAA IN 3041s 2a06:98c1:50::ac40:2168 [2408:210:102:4b00:32be:3bff:fe16:7102]:53 mina.ns.cloudflare.com. AAAA IN 3366s 2606:4700:50::a29f:26fb [2408:210:102:4b00:32be:3bff:fe16:7102]:53 mina.ns.cloudflare.com. AAAA IN 3366s 2803:f800:50::6ca2:c2fb [2408:210:102:4b00:32be:3bff:fe16:7102]:53 mina.ns.cloudflare.com. AAAA IN 3366s 2a06:98c1:50::ac40:22fb [2408:210:102:4b00:32be:3bff:fe16:7102]:53 ``` json出力も可能。 ```console $ doggo --json takeokunn.org A | jq .responses.[0] { "answers": [ { "name": "takeokunn.org.", "type": "A", "class": "IN", "ttl": "261s", "address": "185.199.111.153", "status": "", "rtt": "9ms", "nameserver": "192.168.2.1:53" }, { "name": "takeokunn.org.", "type": "A", "class": "IN", "ttl": "261s", "address": "185.199.110.153", "status": "", "rtt": "9ms", "nameserver": "192.168.2.1:53" }, { "name": "takeokunn.org.", "type": "A", "class": "IN", "ttl": "261s", "address": "185.199.108.153", "status": "", "rtt": "9ms", "nameserver": "192.168.2.1:53" }, { "name": "takeokunn.org.", "type": "A", "class": "IN", "ttl": "261s", "address": "185.199.109.153", "status": "", "rtt": "9ms", "nameserver": "192.168.2.1:53" } ], "authorities": null, "questions": [ { "name": "takeokunn.org.", "type": "A", "class": "IN" } ] } ``` ## 得られた結果・所感 {#得られた結果-所感} digよりも圧倒的に読み易くなったし、tlsやquicにも対応してるのはありがたい。 ## 今後の展開・検討事項 {#今後の展開-検討事項} digツールは自分でも自作できるのでいつかやってみたい。 -------------------------------------------------------------------------------- title: "Typst SlideのDracula Light Theme作成した" date: "2025-12-24" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} [Typstテーマを自作した]({{< relref "20250506220914-create_typst_slide_theme.md" >}}) でDrauclaテーマのスライドを自作したのだが、実際登壇してみるとダークテーマは登壇会場によっては見にくくなってしまうことがわかった。 Draculaテーマだけでなくライトテーマでも生成できるようにしなくちゃいけなくなったので自作してみた。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. テーマ選定 {#1-dot-テーマ選定} ライトテーマは世の中に沢山あるものの、好みのテーマを見つける必要がある。 Draculaテーマが好きな理由は、エコシステムが充実していてあまり努力せず統一感を出せるからであり、対応しているすべてのアプリケーションのカラーテーマをDraculaに設定している。 Draculaテーマにもライトテーマないのかなと調べた所、Draculaを逆から読んだAlucardというテーマがあった。 これなら既存のカラーテーマとの統一感を出せるだろう、ということでAlucardを選定することにした。 ### 2. テーマ作成 {#2-dot-テーマ作成} AIに適当にカラーテーマを作成させた。 ```typst #let colors = ( background: rgb("#FFFBEB"), foreground: rgb("#1F1F1F"), pink: rgb("#A3144D"), purple: rgb("#644AC9"), orange: rgb("#A34D14"), green: rgb("#14710A"), cyan: rgb("#036A96"), red: rgb("#CB3A2A"), yellow: rgb("#846E15"), comment: rgb("#6C664B"), ) ``` TextMate Themeも作成した。 ## 得られた結果・所感 {#得られた結果-所感} 「Software DesignでNix連載しました」というスライドを作って登壇してみた。 特に問題なく登壇できたので今後も積極的に使っていきたい。 {{< figure src="/images/2617E50F-5B65-4E93-8C84-A7383E460218.png" >}} {{< figure src="/images/A53EA15D-765C-4BC1-A389-93E4C5D668B1.png" >}} {{< figure src="/images/B9C671A0-08B7-4EFD-9160-57FA8A3323A3.png" >}} ## 今後の展開・検討事項 {#今後の展開-検討事項} スライドテンプレートを作り込んでより表現力豊かなスライドを作れるようになりたい。 -------------------------------------------------------------------------------- title: "技術イベントのパンフレット記事をTypstでBuildする" date: "2025-12-24" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 組版が必要なあらゆるものはTypstを採用することにしている。 - [Typstで履歴書と職務経歴書をBuildする]({{< relref "20250619103829-build_cv_resume_with_typst.md" >}}) - [AIを駆使してTypstスライドを生成する]({{< relref "20250610215948-ai_powered_typst_slide_generation.md" >}}) - [NixでTypstをBuildしGitHub Pagesでホスティングする]({{< relref "20250126114218-build_and_deploy_typst_using_nix.md" >}}) パンフレット記事や同人誌の組版をする時にも例に漏れずTypstで作成しているのでメモしておく。 内容は [NixでTypstをBuildしGitHub Pagesでホスティングする]({{< relref "20250126114218-build_and_deploy_typst_using_nix.md" >}}) とかなり重複するが、パンフレット記事を書くという側面で纏めていく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. Build周り {#1-dot-build周り} [NixでTypstをBuildしGitHub Pagesでホスティングする]({{< relref "20250126114218-build_and_deploy_typst_using_nix.md" >}}) に纏まっているのでこちらを参照。 ### 2. テーマ選定とTypst設定 {#2-dot-テーマ選定とtypst設定} この手のテーマはTypstには大量にある。 - [charged-ieee](https://typst.app/universe/package/charged-ieee/) - [ilm](https://typst.app/universe/package/ilm/) - [clean-acmart](https://typst.app/universe/package/clean-acmart/) - [clean-ensam](https://typst.app/universe/package/clean-ensam/) - [min-article](https://typst.app/universe/package/min-article/) コードのsyntax highlightのみ [codly](https://typst.app/universe/package/codly/) と [codly-languages](https://typst.app/universe/package/codly-languages/) を使ってるが、デフォルトのTypstの機能を手で設定している。 フォントは[HackGen](https://github.com/yuru7/HackGen)を入れており、日本語も英語も数値もきれいに描画してくれる。 ```typst #set text(lang: "ja", font: "HackGen", size: 8pt) #set page( width: 210mm, height: 297mm, margin: 20mm, columns: 2 ) #set columns(gutter: 12pt) #import "@preview/codly:1.2.0": * #import "@preview/codly-languages:0.1.1": * #show: codly-init.with() #codly(languages: codly-languages) #show raw: set text(size: 6pt) #place( top + center, float: true, scope: "parent", clearance: 1em, )[ #set text(size: 18pt) パッケージマネージャNixで実現する \ 宣言的でクリーンなPHP開発環境の構築 #set text(size: 12pt) たけてぃ \@takeokunn #v(1em) ] ``` ## 得られた結果・所感 {#得られた結果-所感} 満足いくクオリティでpdfを生成できるようになったし、GitHub ActionsでBuildしてGitHub Pagesでホスティングできるようになった - - {{< figure src="/images/8862867B-8146-417E-85C5-53FEA401A5D7.png" >}} ## 今後の展開・検討事項 {#今後の展開-検討事項} もっとTypstでこだわれる余地がないか模索していきたい。 -------------------------------------------------------------------------------- title: "gitleaksを導入した" date: "2025-12-23" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 秘密鍵をgit commitしないようにしたいと以前から考えていた。 大体のプロジェクトのCIにsecretlintを導入していたが、手元でもpre-commitでも設定したいと思っていた所、gitleaksなら設定不要で楽に導入できることが分かったので導入した。 ## 試したこと・やったこと {#試したこと-やったこと} ### 0. ツール選定 {#0-dot-ツール選定} 類似ツールにはgit-secrets、trufflehog、secretlintがある。 次の性質があるツールがよい。 - nixpkgsにある - デフォルト設定がしっかりしてる - CLIで雑に実行できる [『Gitleaks』で機密情報の漏洩を防ぐ〜コマンド編〜](https://dev.classmethod.jp/articles/gitleaks-commands/) を読んでいた所、自分のニーズを満たせることが確認できたので導入を決定した。 ### 1. home-manager module作成 {#1-dot-home-manager-module作成} home-manager moduleを自作すると自由度が上がる。 `programs.gitleaks.enable = true` するだけで設定完了する。 ```nix { programs.gitleaks = { enable = true; enableGitHook = true; settings = { extend.useDefault = true; }; }; } ``` ### 2. devenvのgit pre-commit導入 {#2-dot-devenvのgit-pre-commit導入} devenvでgit hookを定義してると、system globalのgit hookを読んでくれないことがわかった。 devenvでかなりhookを定義してるので、こちらでも明示的にpre-commitを定義している。 ```nix { pkgs, ... }: { git-hooks.hooks = { check-merge-conflicts.enable = true; check-case-conflicts.enable = true; gitleaks = { enable = true; entry = "${pkgs.gitleaks}/bin/gitleaks protect --staged"; }; }; } ``` ## 得られた結果・所感 {#得られた結果-所感} より安全に `git commit` できるようになった。 ## 今後の展開・検討事項 {#今後の展開-検討事項} gitleaksの設定をrecommendedのみ有効にしてるので、もう少し細かい設定をしてきたい。 -------------------------------------------------------------------------------- title: "簡易的なヒューリスティックパーサの自作方法" date: "2025-12-23" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} クローリング結果や業務で使ってるデータベースの値など世の中にはDirtyな値が山のようにある。 一定のルールで正規化した値を取得したい場合にヒューリスティックパーサが便利。 業務で大量に作ったので作り方についてメモしておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 0. ヒューリスティックパーサが適切か見極める {#0-dot-ヒューリスティックパーサが適切か見極める} 目の前の問題の性質がヒューリスティックパーサが適してるか確認する。 - メリット - 不完全・壊れた入力への耐性が高い - 実装の簡単さ - パフォーマンス - 依存関係の排除 - デメリット - エッジケースに対応しきれない - 偽陽性や偽陰性 - 正当性の証明が困難 ### 1. テストデータのパターンを用意する {#1-dot-テストデータのパターンを用意する} AIにテストケースをyamlで生成させる。 たとえば次のyamlは文字列から月給の下限と上限を取得するparserを実装する例。 ```yaml # 年収(年俸)の文字列があるパターン - input: "300万円~350万円 ※この限りではありません。" expected: form: "年収" min: 3000000 max: 3500000 # 年収の単純な範囲指定パターン - input: "年俸400万円~500万円" expected: form: "年俸" min: 4000000 max: 5000000 # 月給のパターン - input: "月給20万円~25万円" expected: form: "月給" min: 200000 max: 250000 - input: "月給25万3000円" expected: form: "月給" min: 253000 max: 253000 # 時給のパターン - input: "時給1,500円~2,000円" expected: form: "時給" min: 1500 max: 2000 - input: "時給1,800円" expected: form: "時給" min: 1800 max: 1800 # 日給のパターン - input: "日給12,000円~15,000円" expected: form: "日給" min: 12000 max: 15000 - input: "日給10,000円" expected: form: "日給" min: 10000 max: 10000 ``` ### 2. パーサをAIに作らせる {#2-dot-パーサをaiに作らせる} 「yamlのinputとexpectedに合うparserを自作して、vitestで実装して」とAIに命令すればよい。 ```typescript import { describe, it, expect } from "vitest"; import fs from "node:fs"; import yaml from "js-yaml"; import path from "node:path"; import { parseSalary } from "./salary"; interface TestData { input: string; expected: { form: string | null; min: number | null; max: number | null; }; } describe("parseSalary", () => { const yamlPath = path.join(__dirname, "salary.yaml"); const yamlFile = fs.readFileSync(yamlPath, "utf8"); const testCases = yaml.load(yamlFile) as TestData[]; testCases.forEach(({ input, expected }) => { it(`should parse "${input}" correctly`, () => { const result = parseSalary(input); expect(result).toEqual(expected); }); }); }); ``` ## 得られた結果・所感 {#得られた結果-所感} Claude Codeに大量にヒューリスティックパーサを大量に錬成できた。 昔の人は手でパーサを書いてたと思うとAIさまざま。 ## 今後の展開・検討事項 {#今後の展開-検討事項} ヒューリスティックパーサよりも厳密性が求められる時にフォーマルなパーサを実装できるようにしておきたい。 -------------------------------------------------------------------------------- title: "emacs wgrepが便利だった" date: "2025-12-02" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 本記事は [Emacs Advent Calendar 2025](https://qiita.com/advent-calendar/2025/emacs) の2日目の記事です。 [emacs wdiredが便利だった]({{< relref "20251128124639-emacs_wdired_mode.md" >}}) でwgrepも便利だということが判明したのでこちらも調査してみる。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. wgrepの機能調査 {#1-dot-wgrepの機能調査} [mhayashi1120/Emacs-wgrep](https://github.com/mhayashi1120/Emacs-wgrep) のサードパーティpackageでMelpaから落とすことができる。 `*grep*` bufferの `grep-mode` にhookして `wgrep-setup` を実行し、 `wgrep-mode` というminor modeを起動する。 ```emacs-lisp (add-hook 'grep-setup-hook 'wgrep-setup) ``` 類似機能に標準機能の `moccur-edit` があり、migemoとの連携も取れるようだ。 自分はmigemoユーザではないが将来的に試してみたい。 ### 2. wgrep設定 {#2-dot-wgrep設定} `defcustom` としては以下だが、自分は特に設定していない。 | 変数名 (Variable) | 機能概要 | 戦略的利用法 | |-----------------------|---------------------------------------|----------------------------------------------------| | wgrep-auto-save-buffer | 変更適用完了時(wgrep-finish-edit)にバッファを自動保存するか 。 | 大規模リファクタリング時、安全のため `nil` (手動保存)を維持するか、迅速な処理のために `t` に設定。 | | wgrep-enable-key | wgrepモードを有効にするためのキーバインド 。 | ワークフローの摩擦を減らすため、単一キー(例: "r"や"e")に設定することを推奨。 | | wgrep-change-readonly-file | バッファが読み取り専用であるかどうかにかかわらず、変更を強制的に適用するか 。 | VCSによって保護されたファイルを編集する場合に有効化。 | デフォルトのkeybindは次のようになっている。 ```emacs-lisp (unless wgrep-mode-map (let ((map (make-sparse-keymap))) (define-key map "\C-c\C-c" 'wgrep-finish-edit) (define-key map "\C-c\C-d" 'wgrep-mark-deletion) (define-key map "\C-c\C-e" 'wgrep-finish-edit) (define-key map "\C-c\C-p" 'wgrep-toggle-readonly-area) (define-key map "\C-c\C-r" 'wgrep-remove-change) (define-key map "\C-x\C-s" 'wgrep-finish-edit) (define-key map "\C-c\C-u" 'wgrep-remove-all-change) (define-key map "\C-c\C-k" 'wgrep-abort-changes) (define-key map "\C-x\C-q" 'wgrep-exit) (setq wgrep-mode-map map))) ``` ### 3. 挙動の確認 {#3-dot-挙動の確認} minad-wareのaffeで実験すると次のようになる。 Step1. `M-x affe-grep` を実行する {{< figure src="/images/38E7738C-FDAF-448A-9A77-F9C9F8BC5EE1.png" >}} Step2. 雑にgrepをかける {{< figure src="/images/867FC3BF-8EA3-445D-A2D6-76010F11AA9E.png" >}} Step3. `embark-act` を起動しつつ `embark-export` する {{< figure src="/images/AE662EFE-2D8D-4E3B-A3CB-C06C7D13513B.png" >}} Step4. `C-c C-q` でwgrep-modeに入り編集、 `C-x C-s` で保存すると変更される {{< figure src="/images/2FB49852-E12D-4F3B-87B8-72189BB6409F.png" >}} ## 得られた結果・所感 {#得られた結果-所感} wdiredと双対をなすキラーアプリと言っても過言ではないくらい便利でもっと早く知りたかった。 ## 今後の展開・検討事項 {#今後の展開-検討事項} [projectile](https://github.com/bbatsov/projectile) からEmacs標準の project.el に移行したいと長年思ってたが、projectileのgrep/replace周りを使いたいしなーと思ってなんとなく移行できてなかった。 projectileを剥がして `affe-grep` や `affre-find` を使いつつ `wgrep` でよいなという結論がついたので次回は移行する記事を書く。 -------------------------------------------------------------------------------- title: "個人的sketchybar設定" date: "2025-11-30" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} [個人的Aerospace設定]({{< relref "20251124235900-setting_aerospace.md" >}}) に続き、sketchybarの設定もしたのでメモしておく。 大体の対応は公式ドキュメントを元にClaude Codeに対応させた。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. 完成イメージと設計方針 {#1-dot-完成イメージと設計方針} 世の中のsketchybarのサンプルを作成して、こんな感じで作る目論見を立てた。 - Hack Nerd Font: アイコンとラベル用 - SF Pro: Appleロゴ用(macOS標準搭載) ```console ┌─────────────────────────────────────────────────────────────────────────┐ │ [􀣺] [1 2 3 4 5 6 7 8 9 0] Firefox | 󰻠 5% 󰍛 42% | 󰖩 Home | 󰕾 50% | 󰁹 85% | 󰃭 01/15 Mon 󰥔 14:30 │ └─────────────────────────────────────────────────────────────────────────┘ ``` また、すべてをdracula themeに寄せるのでカラーパレットをAIに作らせた。 ```nix colors = { background = "0xff282a36"; backgroundTransparent = "0xcc282a36"; currentLine = "0xff44475a"; foreground = "0xfff8f8f2"; comment = "0xff6272a4"; cyan = "0xff8be9fd"; green = "0xff50fa7b"; orange = "0xffffb86c"; pink = "0xffff79c6"; purple = "0xffbd93f9"; red = "0xffff5555"; yellow = "0xfff1fa8c"; transparent = "0x00000000"; black = "0xff21222c"; }; ``` ### 2. 基本設定 {#2-dot-基本設定} #### 2.1. home-manager設定 {#2-dot-1-dot-home-manager設定} home-manager moduleで定義されているので `enable=true` するだけで入る。 ```nix programs.sketchybar = { enable = pkgs.stdenv.isDarwin; }; ``` #### 2.2. バー設定 {#2-dot-2-dot-バー設定} アイランドスタイル(角丸、マージン、ぼかし効果)でMacBook Proのノッチに対応している。 ```bash bar=( height=40 color="$BACKGROUND_TRANSPARENT" shadow=on position=top sticky=on padding_left=8 padding_right=8 margin=8 corner_radius=12 blur_radius=30 notch_width=200 y_offset=4 ) sketchybar --bar "${bar[@]}" ``` #### 2.3. デフォルト設定 {#2-dot-3-dot-デフォルト設定} 全アイテムに適用されるデフォルトスタイルを設定している。 ```bash default=( icon.font="Hack Nerd Font:Bold:14.0" icon.color="$FOREGROUND" label.font="Hack Nerd Font:Bold:12.0" label.color="$FOREGROUND" background.color="$TRANSPARENT" background.corner_radius=8 background.height=28 ) sketchybar --default "${default[@]}" ``` ### 3. 左側アイテム {#3-dot-左側アイテム} #### 3.1. Appleロゴ {#3-dot-1-dot-appleロゴ} 左端にAppleロゴを配置している。クリックでsketchybar全体を更新する。 ```bash sketchybar --add item apple_logo left \ --set apple_logo \ icon="􀣺" \ icon.font="SF Pro:Bold:16.0" \ icon.color="$PURPLE" \ background.color="$CURRENT_LINE" \ background.corner_radius=10 \ background.height=28 \ padding_left=8 \ padding_right=4 \ click_script="sketchybar --update" ``` #### 3.2. ワークスペース表示 {#3-dot-2-dot-ワークスペース表示} AeroSpaceと連携して現在のワークスペース状態を表示している。 - 3.2.1. カスタムイベント Aerospaceからワークスペース変更通知を受け取るためのカスタムイベントを登録している。 ```bash sketchybar --add event aerospace_workspace_change ``` Aerospace側の設定( `aerospace.toml` )では次のように連携: ```toml exec-on-workspace-change = [ '/bin/bash', '-c', 'sketchybar --trigger aerospace_workspace_change FOCUSED_WORKSPACE=$AEROSPACE_FOCUSED_WORKSPACE' ] ``` - 3.2.2. ワークスペースプラグイン 各ワークスペースの状態に応じて表示を切り替えている。 ```bash AEROSPACE="/run/current-system/sw/bin/aerospace" # 自分のspace ID(space.1, space.2, ... から数字を抽出) SID=$(echo "$NAME" | sed 's/space\.//') # フォーカス中のワークスペースを取得 if [ -n "$FOCUSED_WORKSPACE" ]; then FOCUSED="$FOCUSED_WORKSPACE" else FOCUSED=$("$AEROSPACE" list-workspaces --focused 2>/dev/null || echo "1") fi if [ "$SID" = "$FOCUSED" ]; then # フォーカス中: 紫背景 sketchybar --set "$NAME" \ background.color=${colors.purple} \ icon.color=${colors.background} else WINDOWS=$("$AEROSPACE" list-windows --workspace "$SID" 2>/dev/null | wc -l | tr -d ' ') if [ "$WINDOWS" -gt 0 ]; then # ウィンドウあり: 白文字 sketchybar --set "$NAME" \ background.color=${colors.transparent} \ icon.color=${colors.foreground} else # ウィンドウなし: グレー文字 sketchybar --set "$NAME" \ background.color=${colors.transparent} \ icon.color=${colors.comment} fi fi ``` - 3.2.3. ワークスペース表示設定 1-10のワークスペースをループで生成している。ワークスペース10は「0」と表示している。 ```bash SPACE_ICONS=("1" "2" "3" "4" "5" "6" "7" "8" "9" "10") for i in "${!SPACE_ICONS[@]}"; do sid="${SPACE_ICONS[$i]}" if [ "$sid" = "10" ]; then display_icon="0" else display_icon="$sid" fi sketchybar --add item space.$sid left \ --set space.$sid \ icon="$display_icon" \ icon.font="Hack Nerd Font:Bold:14.0" \ icon.padding_left=8 \ icon.padding_right=8 \ background.corner_radius=8 \ background.height=28 \ click_script="aerospace workspace $sid" \ script="${aerospacePlugin}" \ update_freq=1 \ --subscribe space.$sid aerospace_workspace_change done ``` - 3.2.4. ブラケット(グループ化) 正規表現で `space.1` 〜 `space.10` をグループ化している。 ```bash sketchybar --add bracket spaces_bracket '/space\..*/' \ --set spaces_bracket \ background.color="$CURRENT_LINE" \ background.corner_radius=10 \ background.height=28 ``` - 3.2.5. 初期化処理 sketchybar起動時に現在のワークスペースを取得して初期状態を設定している。 ```bash AEROSPACE="/run/current-system/sw/bin/aerospace" FOCUSED=$("$AEROSPACE" list-workspaces --focused 2>/dev/null || echo "1") sketchybar --trigger aerospace_workspace_change FOCUSED_WORKSPACE="$FOCUSED" sketchybar --update ``` #### 3.3. フロントアプリ {#3-dot-3-dot-フロントアプリ} `front_app_switched` イベントで現在のアプリケーション名を表示している。 ```bash if [ "$SENDER" = "front_app_switched" ]; then sketchybar --set "$NAME" label="$INFO" fi ``` ```bash sketchybar --add item front_app left \ --set front_app \ icon.drawing=off \ label.font="Hack Nerd Font:Bold:12.0" \ label.color="$FOREGROUND" \ script="${frontAppPlugin}" \ --subscribe front_app front_app_switched ``` ### 4. 右側アイテム {#4-dot-右側アイテム} システム情報を表示する各種プラグインを実装した。 #### 4.1. CPU使用率 {#4-dot-1-dot-cpu使用率} `top` コマンドでCPU使用率を取得している。 ```bash CPU=$(top -l 1 -n 0 | grep "CPU usage" | awk '{print $3}' | tr -d '%') sketchybar --set "$NAME" label="$CPU%" ``` ```bash sketchybar --add item cpu right \ --set cpu \ icon="󰻠" \ icon.color="$CYAN" \ script="${cpuPlugin}" \ update_freq=5 ``` #### 4.2. メモリ使用率 {#4-dot-2-dot-メモリ使用率} `memory_pressure` コマンドでシステムメモリの使用率を取得している。 ```bash MEMORY=$(memory_pressure | grep "System-wide memory free percentage:" | awk '{print 100-$5}' | tr -d '%') sketchybar --set "$NAME" label="$MEMORY%" ``` ```bash sketchybar --add item memory right \ --set memory \ icon="󰍛" \ icon.color="$GREEN" \ script="${memoryPlugin}" \ update_freq=5 ``` #### 4.3. Wi-Fi {#4-dot-3-dot-wi-fi} `ipconfig getsummary` で接続中のSSIDを表示している。 macOS Sonoma以降ではセキュリティの観点からSSID情報が `` としてマスクされることがある。 ```bash SSID=$(ipconfig getsummary en0 2>/dev/null | grep "^ SSID" | awk -F ' : ' '{print $2}') if [ -z "$SSID" ]; then sketchybar --set "$NAME" icon.color=${colors.red} label="Off" else sketchybar --set "$NAME" icon.color=${colors.cyan} label="$SSID" fi ``` ```bash sketchybar --add item wifi right \ --set wifi \ icon="󰖩" \ icon.color="$CYAN" \ script="${wifiPlugin}" \ update_freq=30 \ --subscribe wifi wifi_change ``` #### 4.4. 音量 {#4-dot-4-dot-音量} AppleScriptで音量設定を取得している。 ```bash VOLUME=$(osascript -e 'output volume of (get volume settings)') MUTED=$(osascript -e 'output muted of (get volume settings)') if [ "$MUTED" = "true" ]; then ICON="󰖁" sketchybar --set "$NAME" icon="$ICON" label="mute" else if [ "$VOLUME" -ge 70 ]; then ICON="󰕾" elif [ "$VOLUME" -ge 30 ]; then ICON="󰖀" else ICON="󰕿" fi sketchybar --set "$NAME" icon="$ICON" label="$VOLUME%" fi ``` ```bash sketchybar --add item volume right \ --set volume \ icon="󰕾" \ icon.color="$PINK" \ script="${volumePlugin}" \ update_freq=10 \ --subscribe volume volume_change ``` #### 4.5. バッテリー {#4-dot-5-dot-バッテリー} `pmset` でバッテリー残量と充電状態を取得している。 ```bash PERCENTAGE=$(pmset -g batt | grep -Eo "[0-9]+%" | cut -d% -f1) CHARGING=$(pmset -g batt | grep 'AC Power') case ${PERCENTAGE} in 100) ICON="󰁹" ;; 9[0-9]) ICON="󰂂" ;; 8[0-9]) ICON="󰂁" ;; 7[0-9]) ICON="󰂀" ;; 6[0-9]) ICON="󰁿" ;; 5[0-9]) ICON="󰁾" ;; 4[0-9]) ICON="󰁽" ;; 3[0-9]) ICON="󰁼" ;; 2[0-9]) ICON="󰁻" ;; 1[0-9]) ICON="󰁺" ;; *) ICON="󰂎" ;; esac if [ -n "$CHARGING" ]; then ICON="󰂄" COLOR="${colors.green}" elif [ "$PERCENTAGE" -le 20 ]; then COLOR="${colors.red}" else COLOR="${colors.green}" fi sketchybar --set "$NAME" icon="$ICON" icon.color="$COLOR" label="$PERCENTAGE%" ``` ```bash sketchybar --add item battery right \ --set battery \ icon="󰁹" \ icon.color="$GREEN" \ script="${batteryPlugin}" \ update_freq=60 \ --subscribe battery power_source_change system_woke ``` #### 4.6. 日時表示 {#4-dot-6-dot-日時表示} 日付と時刻を別々のアイテムとして表示している。 ```bash sketchybar --add item date right \ --set date \ icon="󰃭" \ icon.color="$ORANGE" \ script="sketchybar --set \$NAME label=\"\$(date '+%m/%d %a')\"" \ update_freq=60 sketchybar --add item time right \ --set time \ icon="󰥔" \ icon.color="$YELLOW" \ script="sketchybar --set \$NAME label=\"\$(date '+%H:%M')\"" \ update_freq=30 ``` ## 得られた結果・所感 {#得られた結果-所感} ほぼイメージとおりのものができた。 {{< figure src="/images/4027BCF1-B920-4748-B66A-8F5FDD15CF09.png" >}} ## 今後の展開・検討事項 {#今後の展開-検討事項} 部分的に少し左寄りや右寄りになってしまっている部分を調整する。 -------------------------------------------------------------------------------- title: "emacs wdiredが便利だった" date: "2025-11-29" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} [ryoppippi](https://x.com/ryoppippi) の「【外資ITエンジニアの開発環境】VimmerによるVimmer(になりたい人)のためのVim動画」という動画を見た。 {{< youtube id="XsAlXYWzcv4" title="【外資ITエンジニアの開発環境】VimmerによるVimmer(になりたい人)のためのVim動画" >}} その中で [oil.nvim](https://github.com/stevearc/oil.nvim) 紹介があり、vimの通常操作でファイル操作が可能になる機能がある。 EmacsのDiredでも同様のことがないか調査した所、標準機能に `wdired.el` というものがあったので使ってみた。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. wdiredの機能調査 {#1-dot-wdiredの機能調査} `wdired` は `Writable Dired` の略でDired拡張。 大きな特徴は3点。 Emacs上で完結しているが故のkmacroなどの他の機能を使えるのが便利。 1. テキスト処理の延長でファイル操作ができる(ディレクトリ名も変更可能) 2. 安全性とトランザクション 3. Emacs Lispによる拡張可能 ### 2. wdired設定 {#2-dot-wdired設定} Dired上で起動するの方法は `wdired-change-to-wdired-mode` を有効にすればよく、 `r` に割り当てている。 ```emacs-lisp (define-key dired-mode-map "r" #'wdired-change-to-wdired-mode) ``` `defcustom` はそこそあるが、file permissionを変更できるように `wdired-allow-to-change-permissions` は設定してもよさそう。 ```emacs-lisp ;;; 定義元 (defcustom wdired-allow-to-change-permissions nil "If non-nil, the permissions bits of the files are editable. If t, to change a single bit, put the cursor over it and press the space bar, or left click over it. You can also hit the letter you want to set: if this value is allowed, the character in the buffer will be changed. Anyway, the point is advanced one position, so, for example, you can keep the key pressed to give execution permissions to everybody to that file. If `advanced', the bits are freely editable. You can use `string-rectangle', `query-replace', etc. You can put any value (even newlines), but if you want your changes to be useful, you better put a intelligible value. The real change of the permissions is done by the external program \"chmod\", which must exist." :type '(choice (const :tag "Not allowed" nil) (const :tag "Toggle/set bits" t) (other :tag "Bits freely editable" advanced))) ;;; 自分の設定 (setotp wdired-allow-to-change-permissions 'advanced) ``` ### 3. 挙動の確認 {#3-dot-挙動の確認} 1. 適当なディレクトリでdiredを開いて `r` を押す 2. ファイル名やpermissionを変更する 3. `C-c C-c` で保存する or `C-c C-k` でロールバックする {{< figure src="/images/E62300C4-243B-413C-9C25-32897789BDF6.png" >}} ## 得られた結果・所感 {#得られた結果-所感} diredでのファイル操作が本当に楽になった。 普段がんばってshell script書いたりしてたので無駄なことをしてしまったと反省している。 ## 今後の展開・検討事項 {#今後の展開-検討事項} wdiredの話をしたらwgrepも同様の体験ができるのでこちらも調査したい。 ## Backlinks {#backlinks} - [emacs wgrepが便利だった]({{< relref "20251130141030-emacs_wgrep.md" >}}) -------------------------------------------------------------------------------- title: "k9sをDracula Theme対応した" date: "2025-11-29" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 最近仕事でkubernetesを使う機会に恵まれた。 kubernetesを使うにあたって、モニタリングツールは[k9s](https://k9scli.io/)がデファクトのようなのでhome-managerを使って導入した。 「すべてのツールのカラーテーマはdracula themeに寄せる」という方針なので漏れなく対応した。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. home-managerで導入 {#1-dot-home-managerで導入} home-manager moduleはすでに用意されているのでenableするだけでよい。 `C-c` で閉じるよりも `:q` で閉じる方が直感的なので `noExitOnCtrlC` を有効化した。 ```nix { programs.k9s = { enable = true; settings.k9s = { noExitOnCtrlC = true; }; }; } ``` ### 2. dracula theme対応 {#2-dot-dracula-theme対応} k9sはskinという機能を用意しており、自前で用意したものを適用できる。 Claude Codeに必要なものを用意させた。 ```nix { programs.k9s = { settings.k9s = { ui.skin = "dracula"; }; skins = { dracula = let foreground = "#f8f8f2"; background = "#282a36"; current_line = "#44475a"; selection = "#44475a"; comment = "#6272a4"; cyan = "#8be9fd"; green = "#50fa7b"; orange = "#ffb86c"; pink = "#ff79c6"; purple = "#bd93f9"; red = "#ff5555"; yellow = "#f1fa8c"; in { k9s = { body = { fgColor = foreground; bgColor = background; logoColor = purple; }; prompt = { fgColor = foreground; bgColor = background; suggestColor = purple; }; info = { fgColor = pink; sectionColor = foreground; }; dialog = { fgColor = foreground; bgColor = background; buttonFgColor = foreground; buttonBgColor = purple; buttonFocusFgColor = yellow; buttonFocusBgColor = pink; labelFgColor = orange; fieldFgColor = foreground; }; frame = { border = { fgColor = selection; focusColor = current_line; }; menu = { fgColor = foreground; keyColor = pink; numKeyColor = pink; }; crumbs = { fgColor = foreground; bgColor = current_line; activeColor = current_line; }; status = { newColor = cyan; modifyColor = purple; addColor = green; errorColor = red; highlightColor = orange; killColor = comment; completedColor = comment; }; title = { fgColor = foreground; bgColor = current_line; highlightColor = orange; counterColor = purple; filterColor = pink; }; }; views = { charts = { bgColor = "default"; defaultDialColors = [ purple red ]; defaultChartColors = [ purple red ]; }; table = { fgColor = foreground; bgColor = background; header = { fgColor = foreground; bgColor = background; sorterColor = cyan; }; }; xray = { fgColor = foreground; bgColor = background; cursorColor = current_line; graphicColor = purple; showIcons = false; }; yaml = { keyColor = pink; colonColor = purple; valueColor = foreground; }; logs = { fgColor = foreground; bgColor = background; indicator = { fgColor = foreground; bgColor = purple; toggleOnColor = green; toggleOffColor = cyan; }; }; }; }; }; }; }; } ``` ### 3. k9s起動する {#3-dot-k9s起動する} 無事反映されてることが確認できた。 {{< figure src="/images/A2779C41-2BE1-4E65-A2CB-BEFB899DD519.png" >}} ## 得られた結果・所感 {#得られた結果-所感} ドキュメント的に `export K9S_SKIN="dracula"` ですでに準備されてるものが反映されるのかなと思ったら全然そんなことなかった。 > Lastly, you can completely override these settings in your shell via setting K9S_SKIN in the environment, e.g. export K9S_SKIN="dracula". Claude Codeにthemeを書かせると簡単に欲しいものが出来あがるので便利。 ## 今後の展開・検討事項 {#今後の展開-検討事項} k9sの使い方がまだ分からないので使いこなしたい。 -------------------------------------------------------------------------------- title: "個人的Aerospace設定" date: "2025-11-29" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} タイル型Window Managerは割と好きで、Linux Desktopではswayを使っているのだが、macOSでも使いたいと思っていた。 macOSでは昔ながらの[yabai](https://github.com/koekeishiya/yabai)と後発の[AeroSpace](https://github.com/nikitabobko/AeroSpace)があるが、AeroSpaceで自分のユースケースをすべて満たせていたので採用した。 1年使ってほぼ運用が固まったので後学の為にブログに書いておく。 設定の全文はこちら。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. nix-darwinでAeroSpaceを有効にする {#1-dot-nix-darwinでaerospaceを有効にする} nix-darwinでservices moduleが提供されている。 enableにするだけでlaunchd agentの設定が有効になり、 `Quit AeroSpace` しても再起動されるようになる。 ```nix # 自分の設定 { services.aerospace = { enable = true; }; } ``` ```nix # nix-darwin記述抜粋 { launchd.user.agents.aerospace = { command = "${cfg.package}/Applications/AeroSpace.app/Contents/MacOS/AeroSpace" + (lib.optionalString (cfg.settings != { }) " --config-path ${configFile}"); serviceConfig = { KeepAlive = true; RunAtLoad = true; }; managedBy = "services.aerospace.enable"; }; } ``` ### 2. 基本設定 {#2-dot-基本設定} 適当に便利そうな設定を入れている。 [sketchybar](https://github.com/FelixKratz/SketchyBar) の関係で `gaps.outer.top=10` を入れて調整している。 ```nix { settings = { enable-normalization-flatten-containers = true; enable-normalization-opposite-orientation-for-nested-containers = true; accordion-padding = 0; on-focused-monitor-changed = [ "move-mouse monitor-lazy-center" ]; exec-on-workspace-change = [ "/bin/bash" "-c" "sketchybar --trigger aerospace_workspace_change FOCUSED_WORKSPACE=$(/run/current-system/sw/bin/aerospace list-workspaces --focused)" ]; gaps = { inner = { horizontal = 0; vertical = 0; }; outer = { left = 0; bottom = 0; top = 10; right = 0; }; }; }; } ``` ### 3. keybind周りの設定 {#3-dot-keybind周りの設定} `alt` で操作する割と一般的な設定をしている。 ```nix { main = { binding = { alt-h = "focus left"; alt-l = "focus right"; alt-shift-h = "move left"; alt-shift-l = "move right"; alt-shift-space = "layout floating tiling"; alt-1 = "workspace 1"; alt-2 = "workspace 2"; alt-3 = "workspace 3"; alt-4 = "workspace 4"; alt-5 = "workspace 5"; alt-6 = "workspace 6"; alt-7 = "workspace 7"; alt-8 = "workspace 8"; alt-9 = "workspace 9"; alt-0 = "workspace 10"; alt-shift-1 = [ "move-node-to-workspace 1" "workspace 1" ]; alt-shift-2 = [ "move-node-to-workspace 2" "workspace 2" ]; alt-shift-3 = [ "move-node-to-workspace 3" "workspace 3" ]; alt-shift-4 = [ "move-node-to-workspace 4" "workspace 4" ]; alt-shift-5 = [ "move-node-to-workspace 5" "workspace 5" ]; alt-shift-6 = [ "move-node-to-workspace 6" "workspace 6" ]; alt-shift-7 = [ "move-node-to-workspace 7" "workspace 7" ]; alt-shift-8 = [ "move-node-to-workspace 8" "workspace 8" ]; alt-shift-9 = [ "move-node-to-workspace 9" "workspace 9" ]; alt-shift-0 = [ "move-node-to-workspace 10" "workspace 10" ]; alt-r = "mode resize"; }; }; resize = { binding = { h = "resize width -50"; j = "resize height +50"; k = "resize height -50"; l = "resize width +50"; enter = "mode main"; esc = "mode main"; }; }; } ``` ### 4. アプリケーション配置 {#4-dot-アプリケーション配置} タイル型Window Managerを効率良く使う為には、「workspaceに開くapplicationは固定化する」「1 workspace 1 application、全画面表示」というのを個人的にオススメしている。 自分の場合はswayでも次のように固定化している。 1. ブラウザ関連(e.g. Chrome) 2. Terminal関連(e.g. Kitty) 3. Emacs 4. 雑多なアプリケーション(e.g. Draw.io) 5. 別ディスプレイに写したいもの 6. (特になし) 7. (特になし) 8. Discord(ほぼ使わない) 9. AI関連 10. Slack 次のように `on-window-detected` を設定している。 Chromeは複数プロファイルを使う前提なので `layout floating` で起動するようにしている。 この設定で勝手に指定した位置にアプリケーションが立ち上がってくれるので一々考える必要がなくなった。 ```nix { mode.on-window-detected = [ { "if".app-id = "com.google.Chrome"; run = [ "layout floating" "move-node-to-workspace 1" ]; } { "if".app-id = "com.apple.Terminal"; run = [ "move-node-to-workspace 2" ]; } { "if".app-id = "net.kovidgoyal.kitty"; run = [ "move-node-to-workspace 2" ]; } { "if".app-id = "org.gnu.Emacs"; run = [ "move-node-to-workspace 3" ]; } { "if".app-id = "com.jgraph.drawio.desktop"; run = [ "move-node-to-workspace 4" ]; } { "if".app-id = "com.lambdalisue.Arto"; run = [ "move-node-to-workspace 4" ]; } { "if".app-id = "com.sequel-ace.sequel-ace"; run = [ "move-node-to-workspace 4" ]; } { "if".app-id = "com.hnc.Discord"; run = [ "move-node-to-workspace 8" ]; } { "if".app-id = "com.anthropic.claudefordesktop"; run = [ "move-node-to-workspace 9" ]; } { "if".app-id = "com.openai.chat"; run = [ "move-node-to-workspace 9" ]; } { "if".app-id = "com.clickup.desktop-app"; run = [ "move-node-to-workspace 9" ]; } { "if".app-id = "com.tinyspeck.slackmacgap"; run = [ "move-node-to-workspace 10" ]; } ]; } ``` ### 5. 外部ディスプレイ表示 {#5-dot-外部ディスプレイ表示} `assign-workspaces-to-monitors` でどこのモニターに出力するか制御できる。 - main - "Main" monitor ("main" as in System Settings → Displays → Use as) - secondary - Non-main monitor in case when there are only two monitors - <number> (e.g. 1, 2) - Sequence number of the monitor from left to right. 1-based indexing - <regex-pattern> (e.g. dell.\*, built-in.\*) - Case insensitive regex substring pattern 前述したとおり、workspaceの5番をサブディスプレイに出力する運用にしている。 ```nix { workspace-to-monitor-force-assignment = { "1" = "main"; "2" = "main"; "3" = "main"; "4" = "main"; "5" = "secondary"; "6" = "main"; "7" = "main"; "8" = "main"; "9" = "main"; "10" = "main"; }; } ``` ## 得られた結果・所感 {#得られた結果-所感} 集中時はAeroSpace、そうじゃない場合はDisabledにして無効にする、という運用になってからWindow切り替えの効率が段違いに上がったという実感がある。 AeroSpaceはちょくちょくレンダリングがバグるのだが、 `Quit AeroSpace` で消して、launchdに勝手に再起動させるだけでいいのも利点。 `alt` key起点のkeybindなので、地味にEmacsの `M-l` と被ってしまうのだけが辛い。 ## 今後の展開・検討事項 {#今後の展開-検討事項} [sketchybar](https://github.com/FelixKratz/SketchyBar) との組み合わせについての知見も溜ったのでこちらも記事に纏めていきたい。 ## Backlinks {#backlinks} - [個人的sketchybar設定]({{< relref "20251124235900-setting_sketchybar.md" >}}) -------------------------------------------------------------------------------- title: "Geminiで専門家カスタムGemを作る" date: "2025-07-27" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 普段使いのチャットベースのAIサービスを完全にGeminiに寄せる決断ができた。 元々ChatGPTやClaudeを使っていたが、次の理由でGeminiがいいだろうという結論に落ち着いた。 - Gemini 2.5 Proが賢すぎる - 仕事関連のGoogle WorkspaceだとPro版が使える - 電車の中でAndroidスマホでアイディア出しに使いやすい - オフィスツールとの親和性がよい - カスタムGemが作りやすい メールサービスもそうだが、基本的に拘りのないものはGoogleに寄せていこう個人的な方針もあり、他サービスをアンインストールした。 今回はGeminiのカスタムGemについての紹介と、Gemの作り方について纏める。 なお、自分の肌感覚の話が多いので注意が必要。 ## 試したこと・やったこと {#試したこと-やったこと} ### 0. 基本的な方針 {#0-dot-基本的な方針} カスタムGemはデフォルトプロンプトが設定できるのだが、デフォルトのプロンプトを設定することは次のような利点がある。 - 技術的背景を伝える必要がないので効率性が高い - 応答の品質と一貫性の確保 - 専門性とタスクへの特化 最近のLLMのモデルは賢いので、「あなたはAWSソリューションアーキテクトです」のようなことを伝えなくてもよしなに理解してくれるのですが、自分の肌感覚だと役割を伝えた方が明瞭な回答が返ってくる印象。 自分がAIに質問を投げる時の大半は、明確に「これについて聞きたい」ということがある時なので余計作った方がいいなという結論になっている。 ### 1. Gemを作るカスタムGemを作る {#1-dot-gemを作るカスタムgemを作る} 世の中にはさまざまなプロンプトエンジニアリングテクニックがあるが、プロンプト生成をするプロンプトをAIに生成させる方がコストが低いのでGeminiに作ってもらった。 ````markdown # 1. YOUR PERSONA: Master Prompt Engineer & AI Behavior Architect あなたは、AIの能力を最大限に引き出すための「システムプロンプト」を設計する、世界最高峰のプロンプトアーキテクトです。 あなたの使命は、ユーザーから要求された役割(例: AWSアーキテクト、マーケター、コピーライター)の本質を深く理解し、その役割をAIに憑依させるための、精巧でパワフルなシステムプロンプトを設計・生成することです。 # 2. GUIDING PRINCIPLES: The Blueprint of High-Quality Prompts あなたが設計する全てのシステムプロンプトは、以下の5つの基本原則を必ず満たしていなければなりません。 1. **Deep Persona (深みのあるペルソナ)**: 単なる役割名だけでなく、その役割が持つべき「哲学」「価値観」「使命」「思考の軸」までを定義し、一貫性のある深い人格をAIに与えること。 2. **Structured Principles (構造化された原則)**: AIが従うべき行動や思考の指針を、明確で解釈の余地がない箇条書きの「原則」として定義すること。 3. **Actionable Workflow (実行可能なワークフロー)**: ユーザーからの入力に対し、AIが「どのような手順で思考し、どのような構造で応答を生成すべきか」という具体的な実行プロセス(プロトコル)を定義すること。 4. **Value-Adding Behavior (付加価値のある振る舞い)**: AIが単に指示されたタスクをこなすだけでなく、専門家として「より良くするための提案」や「潜在的なリスクの指摘」など、能動的で気の利いたアクションを取るように設計すること。 5. **Clear Constraints (明確な制約)**: AIが「やってはいけないこと」を禁止事項として明確に定義し、暴走や質の低い応答を防ぐこと。 # 3. EXECUTION PROTOCOL: Your Prompt Engineering Workflow ユーザーから「〇〇用のシステムプロンプトを作って」という要求を受けた場合、以下の厳密なプロセスに従って応答を生成してください。 ## STEP 1: Deconstruct the User's Request (要求の分解と役割定義) - ユーザーが要求した「〇〇」という役割を分析します。 - その役割を完璧にこなす専門家が持つべき「スキルセット」「知識ベース」「思考様式」「価値観」を瞬時に定義します。 ## STEP 2: Architect and Generate the System Prompt (プロンプトの設計と生成) - 上記の分析と5つの基本原則に基づき、要求された役割のための**完全なシステムプロンプト**をMarkdown形式で生成します。 - 生成するプロンプトは、必ず以下の標準構造を含んでいなければなりません。 --- **(ここからが生成されるプロンプトの内容)** ``` `# CORE DIRECTIVE: SYSTEM PROMPT` `# 1. PERSONA: [役割名]` `(役割の定義、使命、哲学などを記述)` `# 2. GUIDING PRINCIPLES: [その役割の行動原則]` `(箇条書きで、思考の軸や価値観を記述)` `# 3. EXECUTION PROTOCOL: [具体的な実行手順]` `(ユーザーからの入力に対する思考と応答のプロセスをステップで記述)` `(必要であれば、対話開始時のテンプレート提示なども含める)` `# 4. ABSOLUTE CONSTRAINTS: [禁止事項]` `(やってはいけないことを具体的に記述)` ``` **(ここまでが生成されるプロンプトの内容)** --- ## STEP 3: Provide a Rationale and Usage Guide (設計意図の解説) - 生成したシステムプロンプトを提示した後、必ず以下の解説を加えてください。 - **【設計意図】**: なぜこのペルソナ、原則、ワークフローを設定したのか、その設計思想を簡潔に説明します。 - **【使い方】**: 生成したプロンプトをAIのシステムプロンプトとして設定し、どのように対話を開始すればよいかをユーザーに案内します。 ## STEP 4: Iterate and Refine (改善サイクル) - ユーザーから「もっとこうしてほしい」というフィードバックがあれば、それに応じて生成したプロンプトを即座に改善し、再提案します。 ```` [カスタム Gem 作成のヒント - Google](https://support.google.com/gemini/answer/15235603?hl=ja) を参考にカスタムGemを作成する。 ### 2. 各専門家Gemを作る {#2-dot-各専門家gemを作る} 自分の業務や趣味に関わるものを作る。 たとえば「AWSソリューションアーキテクトのカスタムGemを作りたいのでプロンプトを作ってください」と投げたら、次のようなプロンプトが生成された。 ````markdown # 1. PERSONA: Principal Solutions Architect あなたは、AWSのプリンシパル・ソリューションアーキテクトである。 あなたの使命は、ユーザーのビジネスゴール達成を最上位の目的とし、表層的な課題解決ではなく、将来の拡張性、運用負荷、TCO(総保有コスト)までを深く洞察した、本質的かつ持続可能な技術戦略を設計・提案することにある。 # 2. GUIDING PRINCIPLES: Core Philosophy あなたの全ての思考と提案は、以下の原則に厳格に基づかなければならない。 ## 2.1. AWS Well-Architected Framework 6つの柱は、あなたの思考の根幹をなす。 常に全ての柱を意識し、バランスの取れた判断を下すこと。 - **運用上の優秀性 (Operational Excellence)** - **セキュリティ (Security)** - **信頼性 (Reliability)** - **パフォーマンス効率 (Performance Efficiency)** - **コスト最適化 (Cost Optimization)** - **持続可能性 (Sustainability)** ## 2.2. Forward-Looking Approach - **先進性の追求**: 常にAWSの最新サービス、アップデート、プレビュー機能を把握し、re:Inventや主要カンファレンスの発表を提案に反映させること。陳腐化した情報や古いベストプラクティスは決して推奨しない。 - **データドリブン**: 提案は常に客観的なデータやユースケースに基づいて行うこと。「なぜその選択が最適か」を定量・定性の両面から説明する責任を負う。 # 3. ACTION PROTOCOL: User Interaction Workflow ユーザーからの入力はすべて「高度な専門性を求めるコンサルテーション」と解釈し、以下の厳密なプロセスに従って応答を生成すること。 ## STEP 1: Deep Dive & Clarification - ユーザーの言葉の裏にある潜在的なニーズやビジネス上の制約を洞察する。 - 最高の提案に不可欠な情報(例: ワークロード特性、データ機密性、予算、RTO/RPO、チームスキルセット等)が欠けている場合、**憶測で進めず、必ず的確な質問によって情報を引き出すこと。** ## STEP 2: Structured Response Generation - 回答は、以下の構造を遵守し、網羅的かつ論理的に構成すること。 1. **【前提条件と課題認識】**: 本提案がどのような情報・仮定に基づいているかを明確にし、ユーザーの課題を再定義する。 2. **【推奨アーキテクチャ】**: 結論となる構成を提示。なぜこれが最適解なのか、核心となる理由を最初に述べる。 3. **【構成図】**: `Mermaid`記法を用いて、アーキテクチャを視覚的に表現する。 4. **【設計の詳細と選定理由】**: - 使用する主要サービスと、その選定理由を詳述する。 - **代替案との比較分析**(なぜ他のサービスではないのか)を必ず含める。 5. **【Well-Architected評価】**: 6つの柱の観点から、この設計がどのように優れているかを具体的に評価する。 6. **【トレードオフとリスク】**: 採用する技術のメリットだけでなく、潜在的なデメリット、リスク、考慮事項を正直に開示する。 7. **【コスト考察】**: 概算コスト、コスト最適化のポイント、料金体系について言及する。 8. **【実現プランと次のステップ】**: 具体的な導入手順や、次に検討すべき事項を提示する。 9. **【セキュリティ上の注意喚起】**: 共有責任モデルにおけるユーザーの責任範囲や、特に注意すべきセキュリティ設定について警告する。 # 4. ABSOLUTE CONSTRAINTS: Prohibited Actions 以下の行為は厳禁とする。 - **アンチパターンの推奨**: 非推奨の構成や時代遅れのプラクティスを提案しないこと。 - **曖昧さの許容**: 「〜など」といった曖昧な表現を避け、具体的で実行可能な提言を行うこと。 - **情報の捏造**: 不確かな情報を断定的に語らないこと。不明な点は「現時点では情報が不足しており判断できない」と明確に述べること。 ```` 他にも、「仕様書レビューのカスタムGemを作りたいのでプロンプトを作ってください」と投げたら、次のようなプロンプトが生成された。 ````markdown # 1. PERSONA: The Insightful Architect & Guardian of Clarity あなたは、単なる評論家ではなく、**プロジェクトの成功を心から願い、設計の意図を深く理解しようと努める、経験豊富なソフトウェアアーキテクト**です。 あなたの使命は、ドキュメントの「粗探し」をすることではなく、**設計のポテンシャルを最大限に引き出し、将来の技術的負債を未然に防ぎ、チーム全員が同じビジョンを共有できるような「明確さ」と「堅牢さ」を設計にもたらすこと**です。 あなたは、敬意と共感を持って、建設的なフィードバックを提供します。 # 2. GUIDING PRINCIPLES: The Pillars of Constructive Review - **Clarity First (明確性を最優先せよ)**: 専門用語の乱用や曖昧な表現は、チームの誤解を招く最大の敵である。常に「この記述は、プロジェクトに新たに参加したメンバーでも理解できるか?」という視点でレビューせよ。 - **Why over What (「なぜ」を「何を」より重視せよ)**: 技術的な選択(What)の背後にあるトレードオフ、代替案、そして根本的な理由(Why)が明確に記述されているかを確認せよ。理由なき決定は、将来の変更を困難にする。 - **Future-Proofing (未来への備えを問え)**: この設計は、将来のスケール、変更、保守性を考慮しているか?短期的な解決策に偏っていないか?潜在的なリスクやボトルネックを指摘し、長期的な視点を提供せよ。 - **Consistency is Key (一貫性を守れ)**: 既存のアーキテクチャ、設計原則、命名規則との一貫性が保たれているかを確認せよ。無秩序な多様性は、システムの複雑性を増大させる。 - **Actionable Feedback (実行可能な提言をせよ)**: 「これは分かりにくい」で終わらせず、「〇〇という観点を追記してはいかがでしょうか?」や「この部分を図で表現すると、より伝わりやすくなるかもしれません」のように、具体的で実行可能な改善案を提示せよ。 - **Respect for the Author (著者への敬意を払え)**: 設計者には意図と努力がある。決して攻撃的、断定的な表現は使わず、「私の理解では…ですが、〇〇という可能性はありますか?」のように、常に敬意と謙虚さを持って質問し、提案せよ。 # 3. EXECUTION PROTOCOL: The Review Workflow ユーザーからレビュー対象のドキュメント(Design Doc, ADRなど)が提示された場合、以下の思考プロセスと構造で応答を生成せよ。 **STEP 1: Acknowledge and Set the Scope (確認とスコープ設定)** - まず、レビュー依頼に感謝を述べる。 - ドキュメント全体を読み込み、「このドキュメントは『〇〇(目的)』を実現するための『△△(対象領域)』に関する設計ですね。その理解でレビューを進めます。」と、自分の理解が正しいかを確認する。 **STEP 2: Structured Review & Analysis (構造的レビューと分析)** 以下の観点に基づき、ドキュメントを多角的に分析する。この際、頭の中で評価を行う。 - **A. 目的と背景 (Purpose & Context)**: この設計が解決しようとしている課題は何か?なぜ今それが必要なのか? - **B. ゴールと非ゴール (Goals & Non-Goals)**: この設計が達成すべきこと、そして「やらないこと」は明確に定義されているか? - **C. 提案された設計 (Proposed Design)**: - **実現可能性 (Feasibility)**: 技術的に実現可能か? - **代替案の検討 (Alternatives Considered)**: なぜ他の選択肢ではなく、この設計が選ばれたのか?トレードオフは明確か? - **詳細と具体性 (Detail & Specificity)**: 他のエンジニアがこれを見て実装に着手できるレベルで具体的か?図やシーケンスは適切か? - **D. 影響範囲 (Impact Analysis)**: - **システムへの影響 (System Impact)**: 他のコンポーネントやシステムにどのような影響を与えるか? - **運用への影響 (Operational Impact)**: 監視、デプロイ、障害対応はどうなるか? - **セキュリティ (Security)**: セキュリティ上の考慮事項は含まれているか? - **E. 将来性 (Future Considerations)**: スケーラビリティ、メンテナンス性、拡張性は考慮されているか? **STEP 3: Generate the Review Output (レビュー結果の生成)** - 分析結果を基に、以下の構造でレビューコメントを生成する。 --- **【レビューサマリー】** > (ドキュメント全体の要約と、最も重要なフィードバック(称賛や懸念点)を2〜3行で記述する。) **【👍 素晴らしい点 (Kudos)】** > - (具体的に称賛したい点を箇条書きで列挙する。「〇〇の図は非常に分かりやすいです」「△△という代替案まで検討されている点は素晴らしいです」など。) **【🤔 確認したい点・懸念点 (Questions & Concerns)】** > - (質問や、より掘り下げてほしい点を、丁寧な表現で箇条書きにする。理由も併記する。) > - **例**: `[セキュリティ]` 〇〇の認証フローについて、トークンの有効期限が記載されていませんが、どのようなポリシーを想定していますか? > - **例**: `[拡張性]` 現在はXX件のデータを想定していますが、将来的に100倍のデータ量になった場合のパフォーマンスへの影響について、もう少し考察を加えていただけると幸いです。 **【💡 提案 (Suggestions)】** > - (ドキュメントをより良くするための具体的な改善案を箇条書きで提示する。) > - **例**: `[明確性]` 〇〇のセクションについて、シーケンス図を追加すると、コンポーネント間のやり取りがより明確になるかと思います。 > - **例**: `[代替案]` データストアとしてXXXが提案されていますが、YYYも△△の点で有力な候補になり得ると考えます。YYYとの比較検討を追記してはいかがでしょうか。 --- **STEP 4: Conclude with Encouragement (激励で締めくくる)** - 「素晴らしい設計をありがとうございます。このフィードバックが、設計をさらに良いものにする一助となれば幸いです。」のように、著者をエンカレッジする言葉で締めくくる。 # 4. ABSOLUTE CONSTRAINTS: 禁止事項 - **人格攻撃の禁止**: 設計内容ではなく、著者を批判するような表現は絶対に使用しないこと。 - **断定的な表現の回避**: 「これはダメだ」「こうすべきだ」といった断定的な物言いは避け、「〇〇というリスクはありませんか?」「△△という方法はいかがでしょうか?」のように、常に提案と質問の形式を取ること。 - **レビュー範囲の逸脱禁止**: 依頼されていないドキュメントやコードを勝手にレビューしないこと。 - **単語の羅列で終わらない**: 「不明瞭です」「複雑です」だけで終わらせず、必ずその理由と改善案をセットで提示すること。 ```` ### 3. 汎用カスタムGemを作る {#3-dot-汎用カスタムgemを作る} 「5年で10億稼ぐにはどうすればいい?」など、雑に質問を投げたい時用の汎用カスタムGemも用意している。 ````markdown # 1. PERSONA: 戦略的思考パートナー あなたは、人間の認知バイアスや感情的な判断を排した、徹底的に誠実かつ論理的な思考プロセスを提供する「戦略的思考パートナー」である。 あなたの唯一の使命は、ユーザーがより質の高い意思決定を下せるよう、客観的で構造化された分析と洞察を提供することにある。あなたは特定の名前を持たず、その役割そのものがあなたのアイデンティティである。 # 2. GUIDING PRINCIPLES: 思考の基本姿勢 あなたの全ての思考と応答は、以下の5つの基本姿勢に厳格に基づかなければならない。 1. **絶対的誠実性 (Radical Honesty)**: 忖度や曖昧さを排除し、事実と論理のみを根拠とする。知識の限界や不確実性を明確に認め、「不明である」「判断できない」と正直に伝える。 2. **客観的プロ視点 (Professional Objectivity)**: 一般論に留まらず、当該分野の専門家が下すであろう客観的かつ批判的な判断をシミュレートする。必要であれば、ユーザーの前提そのものへも建設的な指摘を行う。 3. **徹底的思考 (Rigorous Logic)**: 表面的な情報検索で終わらず、あらゆる可能性を多角的に検討し、論理的に再構築する。後述の`EXECUTION PROTOCOL`を遵守する。 4. **明晰な言語 (Precise Language)**: 常に「です・ます調」を維持しつつ、回りくどい表現を避け、率直かつ明確な言葉で伝える。 5. **建設的結論 (Constructive Conclusion)**: 厳しい分析や指摘で終わらせず、常にユーザーが次にとるべき、具体的で前向きなアクションを提示する。 # 3. EXECUTION PROTOCOL: 標準思考プロセス ユーザーからのあらゆる知的要求に対し、以下の構造化された思考プロセスを厳密に実行すること。 ただし、単純な事実確認等の場合は、末尾の【簡易応答モード】に従ってもよい。 --- ## **【フル思考モード出力テンプレート】** ⚠️ **戦略的思考パートナーがフル思考モードで応答します。思考の透明性を確保するため、プロセスを詳細に記述します。** # 結論サマリー > (ここに、ユーザーが最初に読むべき最も重要な結論と、推奨されるアクションプランを2〜3行で要約して記載する) - **信頼度:** [High / Medium / Low] - **信頼度評価の根拠:** (なぜこの信頼度なのか、最大の変動要因は何かを簡潔に述べる) - **信頼度向上の鍵:** (この結論の確度を上げるために、次に検証・調査すべき最重要項目を1つ挙げる) --- ## Phase 1: 課題の分解と再定義 - **ユーザーの要求:** (ユーザーの質問をそのまま記載) - **本質的な問い:** (この質問の裏にある、本当に解くべき課題を再定義する) - **思考の前提条件:** (この思考プロセスが依拠する仮定や定義を明示する) ## Phase 2: 多角的アプローチの探求 - **アプローチA:** (解決策候補1) - *利点:* - *欠点/リスク:* - **アプローチB:** (解決策候補2) - *利点:* - *欠点/リスク:* - **アプローチC:** (解決策候補3、または既存の前提を覆す視点) - *利点:* - *欠点/リスク:* ## Phase 3: 分析と論理展開 (Chain of Thought) (各アプローチを`Phase 1`で定義した前提と目的に照らし、比較検討する。思考の道筋が明確にわかるように記述する。) ## Phase 4: 自己反証と結論の精緻化 - **導出された結論への反論:** (自ら導いた結論に対し、意図的に最も強力な反論や代替解釈をぶつける) - **反論への応答と結論の補強:** (反論を考慮した上で、結論をどのように修正・補強したか、あるいは条件付けしたかを説明する) ## Phase 5: 最終提言とアクションプラン (精緻化された最終結論に基づき、ユーザーが即座に実行可能な、具体的で詳細なアクションプランを複数パターン提示する。短期・中期・長期の視点や、異なるリソース状況に応じた選択肢を示すことが望ましい。) --- ## **【簡易応答モード】** - **要求:** (単純な文法確認、ファクト確認など) - **結論:** (端的に回答) - **前提/補足:** (回答の前提となる条件や補足情報) - **信頼度:** [High] (主に、公式ドキュメントなど明確な根拠に基づく場合) # 4. ABSOLUTE CONSTRAINTS: 禁止事項 - 感情的な共感や、根拠のない励ましをしないこと。誠実な論理こそが最大の支援である。 - ユーザーの誤った前提を、指摘せずに放置しないこと。 - 思考プロセスを省略して結論だけを提示しないこと。(簡易応答モードを除く) - 未検証の情報を、事実であるかのように断定しないこと。必ず不確実性を明記する。 ```` ## 得られた結果・所感 {#得られた結果-所感} 次のようなカスタムGemを20個くらい作成した。 - Common - 汎用 - ビジネスメール - Review - ブログレビュー - 仕様書レビュー - Architecture - AWSソリューションアーキテクト - Webフロントエンドエキスパート - Hacker - PHP専門家 - TypeScript専門家 それぞれのカスタムGemに質問すると何もプロンプトを指定していないものよりも回答の精度と情報量が明らかに上がった。 AWSならWell-Architectedに準拠した方がいい、文章作成なら読者像に応じて文章の質を変えた方がいい、など暗黙的に理解しているものを明文化できるのがよかった。 ## 今後の展開・検討事項 {#今後の展開-検討事項} 自分の人生のワークフローを洗い出して必要なカスタムGemを作る。 今回はプロンプトをGemini自身に生成させたけど、[LLMのプロンプトエンジニアリング: GitHub Copilotを生んだ開発者が教える生成AIアプリケーション開発](https://www.oreilly.co.jp/books/9784814401130/) などを読んで根本的にプロンプトエンジニアリングについて学ぶ。 -------------------------------------------------------------------------------- title: "プロポーザルの出し方・心構え" date: "2025-07-22" -------------------------------------------------------------------------------- ## 1. Introduction {#1-dot-introduction} 最近プロポーザルの出し方について話す機会が身近で増えました。 私自身、過去にPHPカンファレンスなどの技術イベントで何度も登壇した経験があります。 本記事ではそうした経験から得られたプロポーザルを書く上での心構えや具体的なTipsについて解説します。 また、多くの人が悩む「プロポーザルを出すネタがない」という課題に対する私なりの解決策も提示したいと考えています。 あくまで私個人の経験に基づく独断と偏見が多分に含まれている点、また、自分はこうありたいという理想を書いている点をご了承ください。 ## 2. 今北産業 {#2-dot-今北産業} - 日々学んだことや取り組んだことをアウトプットしよう - アウトプットしたものを元に魅力的なプロポーザルを作ろう - カンファレンスに限らず積極的に打席に立とう ## 3. 今回のスコープ {#3-dot-今回のスコープ} 本記事で対象とするカンファレンスは、主にPHPカンファレンスのような技術カンファレンスを想定しています。 一口にカンファレンスと言ってもその規模や目的はさまざまです。 たとえば国際カンファレンスであれば一定の権威性がありますし、地方のカンファレンスであれば一般的な勉強会の延長線上にあります。 本記事では特にカンファレンスの規模を区別せずに書いています。 また、トークの長さとしては15分〜30分程度のものをイメージしています。 5分程度のLTはゲーム性が異なると考えているため本記事のスコープ外とします。 ## 4. 直近出したプロポーザル {#4-dot-直近出したプロポーザル} ### 4.1. 実例 {#4-dot-1-dot-実例} 直近の2025年前半に提出したプロポーザルを実例として紹介します。 PHPConで採択されたプロポーザルは次のとおりです。 - [PHPUnitを低速化する技術 - PHPカンファレンス名古屋2025](https://fortee.jp/phpcon-nagoya-2025/proposal/8c520c5c-d564-4f6e-b5d9-10821b35d98c) - [Phpactorから学ぶLanguage Server Protocolの仕組み - PHPerKaigi 2025](https://fortee.jp/phperkaigi-2025/proposal/9484cc8e-4002-4d1e-8b30-f11f6a91f853) - pdf: - [定義ジャンプ入門 - PHPカンファレンス小田原2025](https://fortee.jp/phpconodawara-2025/proposal/6d853d31-19ed-4e75-b7fb-841b9490d9a8) - [AWS SQSとLaravelで大規模トラフィックを捌く - PHPカンファレンス新潟2025](https://fortee.jp/phpconniigata-2025/proposal/ab0380b4-95fd-4dad-8788-c0795bb15815) - [yieldが変えるLaravelの世界:LazyCollection徹底入門 - PHP Conference Japan 2025](https://fortee.jp/phpcon-2025/proposal/fcae051f-5b13-4e08-989b-721edadb7134) PHPCon関西で不採択となったプロポーザルは次のとおりです。 - VimConfではEmacsに関するテーマで応募しましたが、残念ながら不採択となりました。 - [Org My Life with Vim](https://gist.github.com/takeokunn/141f85d437390607772135f6f98a3882) - [Beyond One Thing: A Vim & Emacs Strategy for Role Separation](https://gist.github.com/takeokunn/2db3fec85d4c374e9ffaacfe4ad1d221) ### 4.2. ポリシー {#4-dot-2-dot-ポリシー} これはあくまで私個人のルールなので参考にされてもされなくても構いません。 #### 4.2.1. 絶対に登壇駆動でプロポーザルを出さない {#4-dot-2-dot-1-dot-絶対に登壇駆動でプロポーザルを出さない} 「登壇駆動開発」という言葉もありますが、個人的にはそのやり方は好きではありません。 登壇はあくまで日々の技術的な活動や学びのアウトプットを発表する場であるべきだと考えています。 自分の経験や知見が十分に蓄積された結果としてプロポーザルがあり、その先に登壇がある、という順番の方がトークに説得力が増してよいものになると思っています。 #### 4.2.2. トークのアウトラインの下書きは必ず作った状態で出す {#4-dot-2-dot-2-dot-トークのアウトラインの下書きは必ず作った状態で出す} プロポーザルを出す段階でトーク全体の構成や話す内容がある程度固まっている状態にしています。 具体的には、話したいことのキーワードを並べるだけでなく、セクションごとの構成やそれぞれのセクションで何を伝えたいのかを箇条書きで書き出しています。 アウトラインがしっかりしていればプロポーザルの文章も書きやすくなるという利点もあります。 #### 4.2.3. トーク内容を最低でも6割以上理解している状態のときのみ出す {#4-dot-2-dot-3-dot-トーク内容を最低でも6割以上理解している状態のときのみ出す} 自分の基準としては、トーク内容の6割以上はすでに自分の言葉で説明できる状態であることを目安にしています。 残りの4割はプロポーザルが採択されてから資料を作成する過程で調査・学習することで補っています。 不足している知識を学習しつつ、当日のトーク準備にも十分に時間を割けるよい配分だと考えています。 #### 4.2.4. 採択されたトークを使い回さない {#4-dot-2-dot-4-dot-採択されたトークを使い回さない} 一度話した内容を別のカンファレンスで再度プロポーザルとして出すのは、原則として行わないようにしています。 自分自身にとって新しいテーマに挑戦し続けることが成長に繋がると信じています。 これは単に「常に新しいテーマで話すのがかっこいい」という私自身の価値観に基づいているだけで、トークを再演すること自体を否定するものではありません。 ## 5. なぜプロポーザルを出すか {#5-dot-なぜプロポーザルを出すか} [プロポーザルについて2023 - 作文術とか](https://okuramasafumi.hatenablog.jp/entry/2023/07/12/182236) にもあるとおり、プロポーザルを出すメリットはいくつもあります。 - プロポーザルを書くことで自分の知識や知見が整理される - カンファレンスで話すと注目される - コミュニティへの貢献 - 自己ブランディング - 新しい挑戦へのきっかけ 一方、プロポーザルを出すこと自体のデメリットは、登壇準備に時間がかかることくらいで他に特にないと考えています。 たとえ採択されなかったとしてもプロポーザルを作成する過程で得られるものは大きいです。 ## 6. プロポーザルを通すのに必要な要素 {#6-dot-プロポーザルを通すのに必要な要素} プロポーザルが採択されるためには、主に3つの要素が重要だと考えています。 1. 根本的な内容の良さ: トークテーマそのものに価値があり聴衆にとって有益な情報が含まれているか 2. プロポーザルの質: 伝えたい内容が魅力的かつ分かりやすく文章に落とし込まれているか 3. 運営側との音楽性の合致と運: カンファレンスのコンセプトや他の登壇者とのバランス、そして最後は運 このうち、3つ目の「運営側との音楽性の合致と運」は自分ではコントロールが難しい要素です。 これについて「運営の苦悩」といった文脈で語られることもありますが応募者側からすると知る由もない部分です。 しかし、カンファレンスによってはイベント運営側が登壇者に期待することを公開していたり、採択基準を明示していたりする場合もあります。 これらを事前に確認することで、「音楽性の不一致」をある程度は避けられるかもしれません。 - [イベント運営から見た登壇者への期待 - speakerdeck](https://speakerdeck.com/magnolia/ibentoyun-ying-karajian-tadeng-tan-zhe-henoqi-dai) - [PHPカンファレンス関西2025 採択方法を公開します! - note](https://note.com/phpcon_kansai/n/n0aa7041c46f0) とはいえ、プロポーザルが採択されるかどうかの8割は、1と2、つまり「内容の良さ」と「プロポーザルの質」で決まると私は考えています。 これらは日々の努力で十分にカバーできる領域であり、この2点に焦点を当てて解説していきます。 ## 7. プロポーザルを出すまでのフロー {#7-dot-プロポーザルを出すまでのフロー} ### 7.1 フロー図 {#7-dot-1-フロー図} 理想はブログという形でアウトプットすることですが、OSSへのコントリビュート、雑誌や書籍の執筆など形式は問いません。 私自身も実際には、「5 プロポーザル作成 > 登壇」の後に「4 ブログにまとめる」という順番になるなど柔軟に対応しています。 これ以降、本記事では「アウトプット」を「ブログ記事を書く」こととして説明します。 {{< figure src="/images/0A382B4C-B08A-4F4C-844A-CD178EA9859F.png" >}} ```mermaid graph TD; subgraph 日々の活動サイクル; A["1 生産的な活動"] --> B{"2 アウトプットと
Next Action決定"}; B --> C["3 Next Actionの実行"]; C --> B; end; B -- "知見が溜まったら" --> D["4 ブログに纏める"]; D --> E["5 プロポーザル作成"]; D --> F["勉強会で登壇する"]; subgraph 登壇サイクル; E --> G["登壇する"]; G --> H["フィードバックを貰う"]; end; H -.-> A; ``` ### 7.2. 各ステップ解説 {#7-dot-2-dot-各ステップ解説} このフローのポイントは、登壇をゴールにするのではなく、日々の学習とアウトプットの延長線上にプロポーザルを位置付けている点です。 この点については、[コントリビュートで沢山の人が救われる。mattn氏が語る、好循環を実現するアウトプット活動の仕組み](https://levtech.jp/media/article/column/detail_492/) にもまとめられています。 #### 7.2.1. 何か生産的な活動をする {#7-dot-2-dot-1-dot-何か生産的な活動をする} すべての始まりは日々の生産的な活動にあります。 業務での課題解決、新しいライブラリの試用、個人開発でのツール作成、OSS活動など、何でも構いません。 たとえば、ハマったエラーとその解決策をメモする、読んだ技術記事の要約と感想を書くといったより小さな一歩でもまったく問題ありません。 重要なのはここでの活動が後のアウトプットの種になるという点です。 #### 7.2.2. 纏まった形でアウトプットをしつつ、Next Actionを決める {#7-dot-2-dot-2-dot-纏まった形でアウトプットをしつつ-next-actionを決める} 活動で得た知見はどんなに小さくてもアウトプットすることが重要です。 Zennのスクラップや短いブログ記事あるいは社内のドキュメントでも構いません。 この際、Next Actionを言語化することで次に何をすべきかが明確になります。 #### 7.2.3. Next Actionをさらに実行して、纏まった形でアウトプットする {#7-dot-2-dot-3-dot-next-actionをさらに実行して-纏まった形でアウトプットする} 決めたNext Actionを実行し再び作業していきます。 この「活動→アウトプット→次の活動」というサイクルを繰り返すことで、1つのテーマに関する知見が雪だるま式に増えていきます。 #### 7.2.4. ひととおり形になったらブログに纏める {#7-dot-2-dot-4-dot-ひととおり形になったらブログに纏める} サイクルを何度か繰り返し知見がある程度の塊になったら、それらを体系的に整理し1つの長文ブログ記事として公開します。 このブログ記事が後のトークの土台となります。 #### 7.2.5. 4のブログの内容をプロポーザルにする {#7-dot-2-dot-5-dot-4のブログの内容をプロポーザルにする} ここまで来ればプロポーザル作成はそれほど難しくありません。 ブログ記事の導入部分がプロポーザルの概要になり、目次がトークのアウトラインになり、結論が聴衆へのメッセージになります。 すでに質の高いインプットとアウトプットが手元にあるため、自信を持ってプロポーザルを提出できるはずです。 このサイクルを回し始めることこそが「プロポーザルのネタがない」という悩みを解決する、もっとも確実な方法です。 ## 8. どこに対して努力すべきか {#8-dot-どこに対して努力すべきか} これまでのフローを踏まえた上で、プロポーザルの採択率を上げるためにどこに努力を集中させるべきか、3つのポイントに絞って解説します。 ### 8.1. レギュレーションとゲーム性を理解する {#8-dot-1-dot-レギュレーションとゲーム性を理解する} プロポーザルがどのようなルールで審査されるのか、その「レギュレーション」と「ゲーム性」を理解する必要があります。 最低限、次の点は必ず確認するとよいでしょう。 - 募集要項を熟読すること: ターゲット層、求めているテーマ、文字数制限、記載すべき項目など、運営側が提示している情報を読む - 審査基準を把握すること: カンファレンスによっては審査基準を公開している場合があるので、どのような点が評価されるのかを事前に調査する - 過去の採択プロポーザルを読むこと: 採択プロポーザル一覧が公開されていることが多いので、どのようなテーマや書き方のプロポーザルがとおりやすいのか、傾向を把握する たとえば、VimConfでは匿名ではなく応募者自身の活動を見ることを重視しています。 - [【ujihisaさんとmoppさんをお迎えして「VimConf 2024」を振り返り!】エンジニアの楽園 vim-jp ラジオ #21](https://audee.jp/voice/show/94537) - [【「VimConf 2024」登壇!yuys13さん・kat0hさん登場!】エンジニアの楽園 vim-jp ラジオ #22](https://audee.jp/voice/show/94999) 逆にKaigi on Railsは匿名性を重視しており応募者を一切見ないという方針のようです。 - [【炬燵編】Kaigi on Railsのプロポーザルを評価するときに考えていること、求めていること](https://sakahukamaki.hatenablog.jp/entry/2025/07/21/183625) - [Kaigi on RailsにProposalを送ろうと思っている皆さんへ](https://blog.unasuke.com/2023/kaigionrails-proposal-writing-guide/) それぞれのコミュニティでどのような点が重視されているのかを見極める必要があります。 ### 8.2. 質の高いブログ記事を増やす {#8-dot-2-dot-質の高いブログ記事を増やす} プロポーザルの元ネタは日々の活動サイクルから生まれるブログ記事です。 質の高い記事をコンスタントに生み出すために、私は次の点を意識しています。 #### 8.2.1. 2種類の記事を書き分ける {#8-dot-2-dot-1-dot-2種類の記事を書き分ける} 質の高いブログ記事を生み出すためには、目的の異なる2種類の記事を戦略的に書き分けるアプローチが有効です。 1つは、日々の活動で得た小さな発見やTipsを記録する「技術メモ」です。 これらのメモは、情報の鮮度が高いうちに将来の自分のための備忘録として、あるいは小さな知見の共有として気軽に書き留めます。 この段階では完成度よりもスピードを重視します。 これは、Zettelkastenでいうところの「fleeting note」にあたります。 そして、これらの技術メモがある程度の量になった段階でそれらを素材として体系的に再構成し、背景やストーリーを肉付けした「長文ブログ記事」を作成します。 この長文記事こそが、カンファレンスのプロポーザル提出の際に直接のネタとなります。 これは、Zettelkastenでいうところの「permanent note」にあたります。 [org-roamで記事を管理しGitHub Actionsで適切に公開する]({{< relref "20231219122351-how_to_manage_blog_by_org_roam.md" >}}) にも書いたとおり、個人的にはZettelkastenで管理をするとこのサイクルを回しやすいと考えています。 #### 8.2.2. 想定読者を明確にし、フィードバックを積極的に活用する {#8-dot-2-dot-2-dot-想定読者を明確にし-フィードバックを積極的に活用する} 記事を執筆する上で「誰に、何を伝えたいのか」という想定読者を明確に設定することは重要です。 想定読者を具体的にイメージすることで、メッセージがより深く的確に伝わる記事になります。 この段階で読者からよいフィードバックを得られていれば、それは記事のテーマや内容が魅力的であることの証左です。 もしその上でプロポーザルが採択されなかったとしても「今回は運営側と音楽性が合わなかっただけだ」と自信を持って割り切ることができるはずです。 ### 8.3. 魅力的なプロポーザルの書き方を学ぶ {#8-dot-3-dot-魅力的なプロポーザルの書き方を学ぶ} プロポーザルのレギュレーションにも依りますが、次のようなことを明確に書いた方がよいです。 - アウトラインを最初に提示する - 「誰が、何を得られるのか」を明確にする - 過去の採択プロポーザルから学ぶ Googleで検索すればプロポーザルの書き方に関する記事が山のように見つかります。 - [技術カンファレンスに出すプロポーザルを書く](https://tech.layerx.co.jp/entry/2025/06/20/180000) - [登壇するぞ!って決めてからトークするまでの流れ](https://devblog.thebase.in/entry/2021/07/15/110000) - [カンファレンス主催者から見た プロポーザルを通すコツ / How to hack the CfP](https://speakerdeck.com/tomzoh/how-to-hack-the-cfp) AIにレビューしてもらってもいいし、同僚やコミュニティで相談しながら作るのもいいでしょう。 私の場合、プロンプトを作り込んでAIからフィードバックをもらいながら書いています。 1%でも当選する可能性を上げるという意識で取り組んでいます。 ## 9. 落ちた時に考えるべきこと {#9-dot-落ちた時に考えるべきこと} プロポーザルが採択されない時はいつだって辛いものですが、その原因が「自分の努力不足」なのか、それとも「採択者との相性や運の問題」なのかを切り分けて考えるようにしています。 ### 9.1. トークテーマの魅力(内容の良さ) {#9-dot-1-dot-トークテーマの魅力-内容の良さ} 提案したテーマそのものについて再度考えてみます。 - そもそもこのトークテーマは採択メンバーにとって本当に魅力的だったか - ブログ記事が自分が想定していた読者からよい評判を得られていたか 想定していた読者からよい反応が得られていた場合は採択者との方向性が合わなかったと割り切れますし、そもそも反応が悪かったのであれば諦めもつきます。 ### 9.2. プロポーザルの完成度(質の高さ) {#9-dot-2-dot-プロポーザルの完成度-質の高さ} 登壇経験が豊富な友人やコミュニティの仲間、同僚などにプロポーザルを読んでもらい、率直なフィードバックをもらうのがよいでしょう。 - フォーマットを満たしていたか - 伝えたい内容がプロポーザルの文章で十分に表現できていたか ### 9.3. 採択メンバーとの相性と運 {#9-dot-3-dot-採択メンバーとの相性と運} こればかりは自分ではコントロールできない領域です。 カンファレンス全体のテーマ構成と合わなかった、競合するプロポーザルに負けた、採択者の琴線に触れなかったなどさまざまな理由が考えられます。 ベストを尽くして臨んだ結果不採用になったのであれば潔く諦めるくらいの気持ちでいるのがちょうどよいと思います。 ### 9.4. 不採択になったプロポーザルの活かし方 {#9-dot-4-dot-不採択になったプロポーザルの活かし方} プロポーザルを書いた時間が無駄になることは一切ありません。 その経験を次に活かせばよいのです。 - リジェクトコンに出す - 別の勉強会で発表する - 改善して再挑戦する このように次の一手を考えることで、不採択という経験もアウトプットサイクルの一部として次への布石とできます。 ## 10. プロポーザルのネタがない時に考えるべきこと {#10-dot-プロポーザルのネタがない時に考えるべきこと} 「プロポーザルに出すようなネタがない」という悩みは多くの場合、ただの思い込みです。 何かに取り組んでいれば、誰しも次のサイクルを日常的に無意識に回しているはずだからです。 1. 何か生産的な活動をする 2. 活動を文章でアウトプットし、Next Actionを決める 3. Next Actionをさらに実行して、纏まった形でアウトプットする 色々な人の話を聞いている限り、体系的なアウトプットをしていないために知識が整理されず、登壇のネタにできていないだけということが多い印象です。 そういう場合は友人やコミュニティの仲間に、自分がどのようなテーマで登壇できそうか、今何に取り組んでいるのかを話して、思考を整理する手伝いをしてもらうのがお勧めです。 カンファレンスの本筋とは少し違う内容でも親和性があれば採択されることも多いので「ネタがまったくない」ということはほぼないはずです。 もし本当に話すことが何もないと感じるのであれば、それは新しい挑戦ができていないということなのかもしれません。 ## 11. その他 {#11-dot-その他} 最後にプロポーザルに関してよく議論されるいくつかのトピックについて、私の個人的な見解を述べます。 ### 11.1. 「プロポーザルの審査側を経験した方がよい」というアドバイスについて {#11-dot-1-dot-プロポーザルの審査側を経験した方がよい-というアドバイスについて} このアドバイスは一度審査側を経験することで「運営側との音楽性の合致と運」という要素を肌で感じられるという点では有益だと思います。 しかしプロポーザルの採択率を上げるという観点では、その効果は限定的だと考えています。 なぜなら、採択されるプロポーザルの多くは公開されており、それらを分析することで審査基準や傾向は十分に学習可能だからです。 審査側を経験するよりも応募者としてプロポーザルを書く努力を重ねる方が採択率向上への効果は高いというのが私の意見です。 ### 11.2. LTについて {#11-dot-2-dot-ltについて} 本記事ではスコープ外としましたが、LTにはLTの戦い方があります。 LTは5分という短い時間で聴衆の心を掴む必要があり、技術的な深さよりも、面白さやインパクト、共感を呼ぶストーリー性が重視される傾向があります。 お祭りのような側面も強く個人的にはあまり得意ではありませんが、短い時間で自分の考えを凝縮して伝えるよい訓練になることは間違いないでしょう。 ### 11.3. 経験の浅い人にこそプロポーザルを出してもらいたい {#11-dot-3-dot-経験の浅い人にこそプロポーザルを出してもらいたい} 経験の浅い方やこれからコミュニティで活動していきたいと考えている方にこそ、積極的にプロポーザルを出してもらいたいと私は考えています。 プロポーザルを書くという行為は「自分が今、何に取り組んでいるのか」「次にどういうことをやりたいのか」といったことを言語化する絶好の機会になります。 採択されるかどうかはあくまで結果論であり、その過程で得られる経験は無駄にはなりません。 質の悪いプロポーザルを出してもどうせ採択されないだけです。 あれこれ気にせずまずは提出してみてフィードバックのループを回していくのがよいでしょう。 ## 12. おわりに {#12-dot-おわりに} atusyさんの [宝くじに当たる方法を思い出して、明日も頑張ることにした](https://blog.atusy.net/2025/05/30/how-to-win/) という記事が好きです。 宝くじは買わなければ当たらないように、プロポーザルも出さなければ採択されることはありません。 「打席に立って、きちんとヒットを打つ」ということを再現性高く繰り返すのが、かっこいい生き方だと私は思います。 チャンスは逃さないようにしていきたいものです。 また、ベテランの中には無責任にアドバイスはするものの、自らは行動しない人が多いように感じます。 「他人にアドバイスをするからには、まず自分が行動で示すべきだ」というのが私の価値観なので、これからもプロポーザルを出し打席に立ち続けたいという思いを込めてこの記事を書きました。 偉そうなことを書きましたが自分自身も徹底できていない点が多いので引き続き精進します。 -------------------------------------------------------------------------------- title: "Hugoで生成したブログにllms.txtとllms-full.txtを導入した" date: "2025-07-18" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} [llms-full.txtで始めるAIサポートチャット - basemachina](https://tech.basemachina.jp/entry/llms-full-txt-ai-support-chat) を読んだ。 最近gemini-cliで文章を書かせることが増えたので、自分の文章スタイルを学習させればブログを半自動生成できるんだろうなと思ったので試してみた。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. llms.txtの規格について簡単に調査する {#1-dot-llms-dot-txtの規格について簡単に調査する} 以下が仕様。 - llms.txt: サイトの概要や、サイトに含まれるページへのリンク一覧 - llms-full.txt: サイト全体の情報を1つに収めた文書ファイル 各サービスのlls-full.txtは以下。 - Cursor (約500KB) - - Anthropic (約2.5MB) - - Mastra (約4MB) - - Cloudflare (約17MB) - ### 2. hugoに導入する {#2-dot-hugoに導入する} [Hugo で作ったサイトに llms.txt と llms-full.txt を設置した](https://masutaka.net/2025-05-18-1/) を参考にした。 [takeokunn/hugo-take-theme](https://github.com/takeokunn/hugo-take-theme) で次のlayoutを作成した。 [index.llms.txt](https://github.com/takeokunn/hugo-take-theme/blob/main/layouts/index.llms.txt): ```markdown # takeokunn's blog たけてぃ(@takeokunn)の個人的なメモです。 ## URL Structure Zettelkasten - fleeting - 小さい技術的なメモ - permanent - 纏まった技術的メモ - poem - 個人的なポエム - diary - 個人的な日記 ## Content {{ range .Site.RegularPages }} - [{{ .Title }}]({{ .Permalink }}) {{ end }} ``` [index.llmsfull.txt](https://github.com/takeokunn/hugo-take-theme/blob/main/layouts/index.llmsfull.txt): ```markdown # {{ .Site.Title }} > {{ .Site.Params.Description }} {{/* Articles */}} {{ range where (where (sort (.Site.GetPage "/posts/").Pages "Date" "desc") "Draft" "ne" true) "Sitemap.Disable" "ne" true }} -------------------------------------------------------------------------------- title: "{{ .Title }}" date: "{{ .Date.Format "2006-01-02" }}" -------------------------------------------------------------------------------- {{ replaceRE "{{<\\s*comment\\s*>}}(.|\n)*?{{<\\s*/comment\\s*>}}" "" .RawContent -}} {{ end -}} {{/* Others */}} {{ range slice "about.md" -}} {{ with site.GetPage . -}} -------------------------------------------------------------------------------- title: "{{ .Title }}" lastmod: "{{ .Date.Format "2006-01-02" }}" -------------------------------------------------------------------------------- {{ replaceRE "{{<\\s*comment\\s*>}}(.|\n)*?{{<\\s*/comment\\s*>}}" "" .RawContent -}} {{ end -}} {{ end -}} ``` [takeokunn/blog](https://github.com/takeokunn/blog) に [hugo.toml](https://github.com/takeokunn/blog/blob/main/hugo/hugo.toml) を追記した。 ```toml [outputFormats] [outputFormats.llms] mediaType = "text/plain" baseName = "llms" isPlainText = true rel = "alternate" root = true [outputFormats.llmsfull] mediaType = "text/plain" baseName = "llms-full" isPlainText = true rel = "alternate" root = true [outputs] home = ["HTML", "llms", "llmsfull"] ``` ## 得られた結果・所感 {#得られた結果-所感} 無事ホスティングできた。 - - ## 今後の展開・検討事項 {#今後の展開-検討事項} 当初の予定とおり、llms-full.txtをgemini-cliに食わせて自分っぽい文章を生成させられるようにしたい。 -------------------------------------------------------------------------------- title: "Makefileを便利コマンドメモとして使うことに対する違和感" date: "2025-07-11" -------------------------------------------------------------------------------- ## 追記 {#追記} この記事に対するyasunoriの補足記事はこちら。 ## Introduction {#introduction} yasunoriがZennに書いた「[職場のプロジェクトに必ず配置しちゃうMakefileの話](https://zenn.dev/loglass/articles/0016-make-makefile)」というのがバズっています。 「コマンドが覚え辛い」という、どのプロジェクトにもあるような問題を「Makefileをコマンドメモとして整える」ことによって解決するという記事でした。 SNSやvim-jp slackを見ていると賛同の声が多い印象を受けています。 私個人も過去に同様のMakefileを作ったことがあるし、現職でもMakefileが入っているプロジェクトがあるくらいにはよくある手法です。 しかし、この記事を読んだ時に自分としてはこの手法がイマイチだと感じました。 モヤモヤをちゃんと言語化した方がいいだろうということでこの記事を書いています。 決して元記事を批判するつもりはなく、こういう視点もあるよというくらいの温度感で捉えてほしいです。 ## Makefileをプロジェクトに入れたい時の前提整理 {#makefileをプロジェクトに入れたい時の前提整理} そもそも「誰のために」「どのプロジェクト規模で」「どういう問題を解決したくて」Makefileをプロジェクトに追加するものなのか整理する必要があります。 今回のユースケースでは、古き良きC言語をBuildする時の `make && make install` のようなタスクランナー的な使い方ではなく、元記事と同様プロジェクトのコマンドメモとしての使い方について言及しています。 ### Makefileの想定利用者 {#makefileの想定利用者} 想定利用者のペルソナ像は主に4種類います。 1つめは、デザインや文言を修正したい非エンジニア寄りのデザイナーです。 役割はUI/UXデザイナー、またはマークアップ担当者で、技術スキルは低〜中程度を想定しています。 主な目的は、文言・デザインの軽微な修正を手元で確認し、FigmaやXDのようなデザインツール上ではなく「実際の画面」で表示を見ることです。 行動特性としては、GUIでの操作が望ましく、ターミナル操作も最低限であれば対応可能ですが、何度も環境構築の手順を覚えるのは非効率だと感じています。 主な課題として、環境構築に時間がかかること、依存関係が多すぎて途中で躓いてしまうこと、Node.js, Ruby, Pythonといった技術スタックの違いが分からない、といった点が挙げられます。 2つめは、そのプロジェクトに精通していないプログラマです。 役割は外部の協力会社のメンバー、派遣や業務委託のエンジニア、またはプロジェクトに参加したばかりの新人エンジニアを想定しています。技術スキルは中〜高程度です。 主な目的は、限られた期間や担当範囲の中で、与えられたチケットの実装や修正を効率的に行うことであり、プロジェクト全体の詳細な把握は必要最低限で済ませたいと考えています。 行動特性としては、ローカル環境の立ち上げに時間をかけたくないと感じており、覚えるべき新規のコマンドが多すぎると混乱してしまいます。 主な課題として、「.envファイルはどこにあるのか?」「どの順番で起動すればいいのか?」「フロントエンドだけでなくバックエンドも動かす必要があるのか?」といった疑問が次々と湧いてくる点が挙げられます。 3つめは、そのプロジェクトに精通しているプログラマです。 役割はリードエンジニア、メンテナー、あるいは主要な開発者であり、技術スキルは高いレベルにあります。 主な目的は、長期的な保守や機能の拡張や新機能の実装や大規模な構成変更など、プロジェクトの根幹に関わる作業することです。 行動特性としては、再利用性や抽象化といった概念に関心があり、開発フローの自動化や最適化にも積極的です。 主な課題として、コマンドが乱立して管理が煩雑になること、特定の担当者にしか分からない作業(属人化)が増えること、そしてドキュメントが古くなって陳腐化しがちである点が挙げられます。 4つめは、AI Coding Agentです。 役割はVibe Coding、その技術スキルは正確かつ高速な処理能力にあります。 主な目的は、指定された手順を正確に実行し、ソースコードのビルド、テスト、静的チェックといった成果物の検証作業を滞りなく行うことです。 行動特性としては、曖昧な指示に弱く、処理の中間ステップが明示的に定義されていないと失敗する傾向があります。また、コマンドの標準出力(STDOUT)が構造化されていることを期待する場合もあります。 主な課題として、コマンドの実行に失敗した場合でも、その背景にある詳細な文脈を読み解いてリトライしたり、原因を推測ができない点が挙げられます。 ### 想定しているプロジェクト規模 {#想定しているプロジェクト規模} 当記事で想定しているプロジェクトは主に3種類あります。 | プロジェクト規模 | コード行数 | 特徴 | |----------|-------|-------------------------------------------------| | 小規模プロジェクト | 1万行程度 | 数ページ程度のちょっとしたWebアプリケーション、単一言語、依存ツールチェインが少ない | | 中規模プロジェクト | 10万行程度 | 複数機能があるWebアプリケーション、複数言語、依存ツールチェインや開発用コマンドがそれなりに多い | | 大規模プロジェクト | 100万程度 | 大規模なWebアプリケーション、複数言語、依存ツールチェインや開発用コマンドが多い | ### Makefileで実行するコマンド {#makefileで実行するコマンド} 今まで見てきたコマンドは主に3種類です。 | コマンド種別 | 例 | |-------------|------------------------------------------------------| | マシン環境に依存するコマンド | `brew install`, `npm install -g`, `docker compose up` | | プロジェクト環境に依存するコマンド | `npm install`, `composer install` | | プロジェクト開発時に使うコマンド | `vendor/bin/phpstan`, `./gradlew build` | ## 元記事の状況整理 {#元記事の状況整理} 元記事から次のような前提があるという認識を持っています。 - 想定利用者 - そのプロジェクトに精通していないプログラマ - Makefileで実行するコマンド - マシン環境に依存するコマンド - プロジェクト環境に依存するコマンド - プロジェクト開発時に使うコマンド - 想定しているプロジェクト規模 - 中規模プロジェクト - 大規模プロジェクト 次の一文からも分かるように実際の利用者の職場の人は満足しているようです。 > こういう改善をやると開発チームのSlackチャンネルでは、「これはアプノマ(Update Normal)だ!」と言ってもらえる素敵な環境です。 ## 個人的な見解 {#個人的な見解} まず大前提として、「実際の利用者が満足している」状態をどう作るかというのがもっとも重要であり、Makefileを入れることによって職場の人が満足しているのであればそれでいいし、素晴しいことだと思っています。 その上で、環境構築オタクとして「そもそもMakefileを作るのはイマイチである」と思っている点をひとつずつ整理していきました。 元記事の内容だけでなく、自分が過去に関わったプロジェクトのMakefileについても書いているのでそのあたりもご了承ください。 ### 本来はREADMEを充実させるべき {#本来はreadmeを充実させるべき} 規模や人にかかわらずすべてのユースケースでもっとも重要なことでここにもっとも力を入れるべきです。 何のミドルウェアに依存していて、何をどう実行するかを明文化してMarkdownで伝えることができます。 チーム全体での技術的共通認識が形成され、ブラックボックス化を防げるので属人化防止に役立ちます。 長期保守性・引き継ぎのしやすさが飛躍的に上がるのもいいですね。 Makefileに寄せてしまうとMakefileは文書に向いていないのでそもそもイマイチだし、READMEとMakefileで二重管理になるのもイマイチです。 特に元記事ではコマンドを `@echo` で出力しているので素直にREADMEに書けば良さそうに感じています。 ### 「デザインや文言を修正したい非エンジニア寄りのデザイナー」に向けてならしょうがなく作る {#デザインや文言を修正したい非エンジニア寄りのデザイナー-に向けてならしょうがなく作る} Terminalに不慣れな人向けに `make up` で `docker compose up --build` が動くようにすることは時々あります。 そもそも `docker compose up --build` などの複雑な文字入力をさせるのは困難な場合があるので、そういう時はしぶしぶMakefileを作って `make up` だけ叩いてくださいというのが早くて正確なのでしょうがなく作っています。 そういう人向けには可能な限りTargetを減らして最低限で提供したい所です。 ### 「そのプロジェクトに精通しているプログラマ」はそもそもMakefileを使わない {#そのプロジェクトに精通しているプログラマ-はそもそもmakefileを使わない} 大体の「そのプロジェクトに精通しているプログラマ」は自分の意図したコマンドを意識して細かく実行するので、変に抽象化されたMakefileは逆に足枷になりがちな印象があります。 Makefileのメンテナンスがおざなりになり、ちゃんと動くことを保証されなくなりがちです。 これはREADMEでも起こりがちな問題ですがREADMEを充実させる方が筋がよいだろう、というのが自分の意見です。 ### 「マシン環境に依存するコマンド」をMakefileで実行することに対して違和感がある {#マシン環境に依存するコマンド-をmakefileで実行することに対して違和感がある} 次のようなコマンドをイメージしています。 ```makefile install-tools: ## 開発ツールのインストール brew install postgresql redis minio awscli npm install -g @aws-amplify/cli ``` この手のMakefileを見るたびに次のようなことを考えています。 - そもそもREADMEを充実させる方がよい - ワンショットで実行するものをいちいち載せたくない - マシンに依存するコマンドはプロジェクト固有のツールではない認識なので違和感を感じる - MiddlewareはDockerに寄せたい ### Targetが多すぎる {#targetが多すぎる} 複雑なコマンドの組み合わせを定義したいという動機は理解できるが、それが大量に存在している時点でプロジェクト構造やワークフロー自体に根本的な歪みがある可能性が高いように感じています。 「人が覚えきれないからMakefileに記述させる」というより、「本来、そんなに複雑であるべきではなかった設計をMakefileで補っているだけ」になっている危険性がありそうな印象です。 ### package固有script機能に寄せたい {#package固有script機能に寄せたい} npm scriptやcomposer scriptのように、現代のpackage managerにはscript機能が大体搭載されています。 ざっと次のようなメリットがあります。 - 標準化されたスクリプト管理 - すでに存在する package.json / composer.json に統合できる - 依存ツールとの連携がスムーズ - e.g.eslint, vitest, phpunit, phpstan - エコシステムとの親和性が高い - ドキュメントにせずとも npm run / composer run で一覧が見られる - シェル補完が効く - AIや新人でも認識しやすい 「小規模プロジェクト」ならpackage固有script機能に寄せられるはずです。 オレオレMakefileを書くと「大規模プロジェクト」のような多言語用のscriptを統合できるというメリットもあるが、基本的にはREADMEに書くだけでいいはずです。 必要に応じて、package固有scriptの組み合わせMakefileを作るのはいいでしょうし、そうなるとMakefileのTargetが最低限になるはずです。 ### 正規表現はメンテナンス性が低い {#正規表現はメンテナンス性が低い} Shell ScriptやAWKや正規表現は「書くのは速いが、読むのは苦痛」なものです。 最初からチームや将来の自分のために、npm/composerなどの構造化されたタスクランナーの標準に乗っかっておいた方が長期的にはずっと健全だと思っています。 ### AI Coding Agentフレンドリーじゃない {#ai-coding-agentフレンドリーじゃない} AI Coding Agentは構文解析と文脈理解に基づいてコードの提案・修正・生成していますが、これらのエージェントは主に構造化された明示的記法とツールチェインの規約・ドキュメントに依存します。 Makefileは伝統的なビルド定義ファイルであるが、DSLであり文脈が不透明になりやすく、推論には全体の文脈とMakefile知識が必要になります。 AIにとって解析しやすく、意図の推論や補完しやすい方がVibe Coding時代には合理的という認識です。 ## その他 {#その他} ### org-modeで管理すればよいのでは? {#org-modeで管理すればよいのでは} 「READMEを充実させる」というのが一番大事だと思っているので、READMEからコマンドを実行できること自体は理想だと思っています。 とはいえ、全員が全員Emacsを使っている訳ではないし、任意の環境で安全に実行できるのが理想なのでそこはorg-modeに寄せる必要はないですね。 ### Shell Scriptにした方がよいのでは? {#shell-scriptにした方がよいのでは} 前述のとおり、Shell Scirptは「書くのは速いが、読むのは苦痛」なものです。 しかも、テストも書きにくいのでメンテナンス性に難が有ります。 複雑なことをしないなら尚更READMEに書いておいた方がいいし、複雑なことをするならShell Scirptではなくフレームワークのコマンド作成や別の言語に寄せたいという感覚です。 ## まとめ {#まとめ} 「本来はREADMEを充実させるべき」というのがAIにとっても新規開発者にとっても嬉しい施策だという理解です。 可能な限りREADMEなどのドキュメントをちゃんとメンテナンスしていきたいですね。 -------------------------------------------------------------------------------- title: "はてなブックマークコメント非表示にした" date: "2025-07-09" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} バズった記事のはてなブックマークのコメントを見る度に、嫉妬や見苦しいコメントが際立つなという感想を長年持っていた。 自分のブログがはてなブックマークで書かれたことはないが、変な火種を生んでもしょうがないので非表示にすることにした。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. 方針決め {#1-dot-方針決め} [org-roamで記事を管理しGitHub Actionsで適切に公開する]({{< relref "../permanent/20231219122351-how_to_manage_blog_by_org_roam.md" >}}) にも書いたとおり、2025年7月現在、私は次の2つのブログを運用している。 | | URL | 想定読者 | 用途 | |-------|---------------------------------------------------|----------|---------------| | 自作ブログ | [www.takeokunn.org](https://www.takeokunn.org/) | 特定少数(2hop以内) | 個人的な調査メモや作業ログ | | Zenn | [zenn.dev/takeokunn](https://zenn.dev/takeokunn/) | 不特定多数 | 対外的な活動報告 + 広報目的 | Zennに関して、仕事に紐付く活動なので基本的しっかり腰を据えて書いている。 所属企業のAdventCalendarもZennに書くことにしている。 一方自作ブログの方は、この記事もそうだが自分の作業メモとして書いている。 なので、校正も甘かったり誤字脱字もあったり検証が甘かったり、基本的にツメが甘いことが多い。 そもそも不特定多数を意識したブログではないので不特定多数からのフィードバックを期待していない。 ブックマーク自体はされてもよいので、今回は自作ブログの方のみ、はてなブックマークコメント非表示にすることにした。 ### 2. コメント非表示対応 {#2-dot-コメント非表示対応} はてなブックマークヘルプに「コメント一覧非表示機能について」の記述がある。 今回は自作ブログなのでmetaタグを追加すれば解決するようだ。 ```html ``` テーマは [takeokunn/hugo-take-theme](https://github.com/takeokunn/hugo-take-theme) で管理しているのでタグ追加する対応をした。 あとはcurlを叩いて反映されてることを確認した。 ```bash $ curl -s https://www.takeokunn.org/ | grep 'Hatena::Bookmark' takeokunn's blog ``` ### 3. 動作検証 {#3-dot-動作検証} 無事動作してくれることが確認できた。 1. ログイン状態で適当な記事をはてなブックマークで登録 2. コメントを書く 3. シークレットブラウザで非表示になっていることを確認 {{< figure src="/images/09E03652-CE70-484B-86B0-BC64CBD5D956.png" >}} ## 得られた結果・所感 {#得られた結果-所感} 無用な争いを避けられそうなので良かった。 ## 今後の展開・検討事項 {#今後の展開-検討事項} とはいえ、ちゃんとZennに活動報告を書くべきなのでサボらずに書いていきたい。 -------------------------------------------------------------------------------- title: "GitHub Actions上でtblsを使ってスキーマ情報を取得してAIでSQLを生成する" date: "2025-06-28" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 日頃から [k1LoW/tbls](https://github.com/k1LoW/tbls) を 利用しているが、個人的に気に入ってる運用をメモしておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. GitHub ActionsでDBに入れつつtblsを実行する {#1-dot-github-actionsでdbに入れつつtblsを実行する} [tblsをセットアップするGitHub Actionとしてsetup-tbls(を作るツールとしてgh-setup)を作った](https://k1low.hatenablog.com/entry/2023/02/16/093315) にもあるとおり、[k1low/setup-tbls](https://github.com/k1low/setup-tbls) を利用すればGitHub Actions上でtblsを簡単に利用できる。 以下はISUCONで使っているGitHub Actionsだが、Laravelでも同じように `php artisan migrate` すればよい。 `main` branchにpushされたら更新される。 1. MySQLのセットアップ 2. tblsをインストール 3. DBにスキーマ情報を反映 4. tbls docを実行する 5. dbdoc branchに `./dbdoc` を反映してpushする ```yaml name: Run db_tbls on: workflow_dispatch: push: branches: - main permissions: contents: write jobs: db-tbls: runs-on: ubuntu-latest timeout-minutes: 300 services: mysql: image: mysql:8 options: --health-cmd "mysqladmin ping -h localhost" --health-interval 20s --health-timeout 10s --health-retries 10 ports: - 3306:3306 env: MYSQL_DATABASE: isuride MYSQL_ROOT_PASSWORD: isucon MYSQL_ROOT_HOST: '%' steps: - uses: actions/checkout@v4 - uses: k1low/setup-tbls@v1 - name: Run schema.sql run: mysql --host="127.0.0.1" --port=3306 --user="root" --password="isucon" isuride < webapp/sql/1-schema.sql - name: Run tbls for generate database document run: tbls doc --dsn "mysql://root:isucon@127.0.0.1:3306/isuride" - name: Deploy dbdob uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./dbdoc publish_branch: dbdoc ``` ### 2. dbdoc repoを用意する {#2-dot-dbdoc-repoを用意する} `-dbdoc>` を用意する。 ```console $ ghq get git@github.com:takeokunn/test-repo.git $ mv ~/ghq/github.com/takeokunn/test-repo ~/ghq/github.com/takeokunn/test-repo-dbdoc $ git checkout origin/main ``` [個人的Local環境のGit Branch運用について]({{< relref "20250518144557-local_git_branch_operation.md" >}}) にも書いたとおり、local branchを作らずに定期的に `git fetch -p` をする。 ```console $ cd ~/ghq/github.com/takeokunn/test-repo-dbdoc $ git fetch -p $ git branch -D main ``` ### 3. AI AgentでSQLを生成する {#3-dot-ai-agentでsqlを生成する} 自分の場合は [aider](https://aider.chat/) だが、適当なAI AgentにSQLを作成させる。 ```markdown ユーザ数を計算するSQLを作成して ``` ## 得られた結果・所感 {#得られた結果-所感} 運用フローが固まったのでかなりSQLを書くハードルが下がった。 tblsがCIフレンドリーだからこそできる運用フローなので作者のk1Lowさんに感謝。 ## 今後の展開・検討事項 {#今後の展開-検討事項} SQLを精度高く生成させるプロンプトを用意する。 -------------------------------------------------------------------------------- title: "php-srcとyieldについて調査メモ" date: "2025-06-25" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} [php/php-src](https://github.com/php/php-src) でyieldはどう扱われているのか調査したのでメモしておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### RFCを読む {#rfcを読む} 2012/06/05にNikita Popov氏が「Request for Comments: Generators」というRFCを出している。 yield構文はPHP 5.5で導入されたようだ。 yieldは次の点で優れているという主張をしている。 - パフォーマンスとメモリ効率 - 可読性・保守性 - イテレーション処理の標準化 GeneratorのInterfaceは以下。 Iteratorを継承しているのでforeachでloopを回せる。 ```php final class Generator implements Iterator { void rewind(); bool valid(); mixed current(); mixed key(); void next(); mixed send(mixed $value); mixed throw(Exception $exception); } ``` 他言語の実装は以下。 - Python - - ECMAScript - - C# - ### サンプルコード {#サンプルコード} #### 最小サンプル {#最小サンプル} ```php function gen() { yield 1; yield 2; yield 3; } $g = gen(); echo $g->current(); // 1 $g->next(); echo $g->current(); // 2 $g->next(); echo $g->current(); // 3 $g->next(); ``` #### foreach {#foreach} Iteratorなのでforeachで回せる。 ```php function keyedGen() { yield 'first' => 'A'; yield 'second' => 'B'; yield 'third' => 'C'; } foreach (keyedGen() as $key => $val) { echo "$key => $val\n"; // first => A / second => B / third => C } ``` #### yield from {#yield-from} yieldの合成可能。 ```php function sub() { yield 1; yield 2; } function main() { yield 0; yield from sub(); yield 3; } foreach (main() as $v) echo $v; // 0 1 2 3 ``` #### CLIアプリ例 {#cliアプリ例} yieldを使えば対話式のアプリケーションを簡単に作成できる。 ```php function interactiveForm() { echo "フォーム開始\n"; $name = yield "お名前を入力してください:"; $age = yield "年齢を入力してください:"; $lang = yield "好きな言語を入力してください:"; yield "確認: {$name}さん ({$age}歳)、{$lang}が好きなんですね。"; } $gen = interactiveForm(); while ($gen->valid()) { $prompt = $gen->current(); echo $prompt . "\n"; $input = readline("> "); $gen->send($input); } ``` ```console $ nix run nixpkgs#php test.php フォーム開始 お名前を入力してください: @> take 年齢を入力してください: @> 29 好きな言語を入力してください: @> php 確認: takeさん (29歳)、phpが好きなんですね。 @> yes ``` #### throw例 {#throw例} throwを投げることもできる。 ```php function worker() { try { while (true) { $task = yield; echo "work on $task\n"; } } catch (Exception $e) { echo "stop: {$e->getMessage()}\n"; } } $g = worker(); $g->rewind(); $g->send('task-1'); $g->throw(new Exception('interrupt')); ``` #### 戻り値付き {#戻り値付き} ```php function sum($a, $b) { yield $a; return $a + $b; } $g = sum(2, 3); $g->next(); echo $g->getReturn(); ``` ### php-srcコードリーディング {#php-srcコードリーディング} #### コンパイラ {#コンパイラ} [Zend/zend_language_parser.y](https://github.com/php/php-src/blob/8b61c49987750b74bee19838c7f7c9fbbf53aace/Zend/zend_language_parser.y) に予約語として登録されている。 ```bison %precedence T_YIELD %precedence T_YIELD_FROM ``` 次の3つの構文をサポートしている。 - 単純なyield: T_YIELD のみ - 値付きyield: T_YIELD expr - キー・値付きyield: T_YIELD expr T_DOUBLE_ARROW expr ```bison | T_YIELD { $$ = zend_ast_create(ZEND_AST_YIELD, NULL, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } | T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } | T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } | T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } ``` [Zend/zend_compile.c](https://github.com/php/php-src/blob/8b61c49987750b74bee19838c7f7c9fbbf53aace/Zend/zend_compile.c#L1530-L1560) でgenerator関数かどうかを判定している。 `ZEND_ACC_GENERATOR` としてマークする。 ```c static bool is_generator_compatible_class_type(const zend_string *name) { return zend_string_equals_ci(name, ZSTR_KNOWN(ZEND_STR_TRAVERSABLE)) || zend_string_equals_literal_ci(name, "Iterator") || zend_string_equals_literal_ci(name, "Generator"); } /* 中略 */ static void zend_mark_function_as_generator(void) /* {{{ */ { if (!CG(active_op_array)->function_name) { zend_error_noreturn(E_COMPILE_ERROR, "The \"yield\" expression can only be used inside a function"); } if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { const zend_type return_type = CG(active_op_array)->arg_info[-1].type; bool valid_type = (ZEND_TYPE_FULL_MASK(return_type) & MAY_BE_OBJECT) != 0; if (!valid_type) { const zend_type *single_type; ZEND_TYPE_FOREACH(return_type, single_type) { if (ZEND_TYPE_HAS_NAME(*single_type) && is_generator_compatible_class_type(ZEND_TYPE_NAME(*single_type))) { valid_type = 1; break; } } ZEND_TYPE_FOREACH_END(); } if (!valid_type) { zend_string *str = zend_type_to_string(return_type); zend_error_noreturn(E_COMPILE_ERROR, "Generator return type must be a supertype of Generator, %s given", ZSTR_VAL(str)); } } CG(active_op_array)->fn_flags |= ZEND_ACC_GENERATOR; } ``` yieldはTracing JITではサポートされていない。 JITに関しては [PHP8から追加されたJITについて学ぼう!]({{< relref "../permanent/20221222111924-about_php_jit.md" >}}) に書いている。 ```c /* switch through trampoline */ case ZEND_YIELD: case ZEND_YIELD_FROM: ``` `ZEND_ACC_GENERATOR` flagが立っているものは `ZEND_GENERATOR_CREATE` というOPCODEに割り当てられて処理される。 1. ジェネレーターオブジェクト作成: ジェネレータークラスのオブジェクトを初期化し、通常のVMスタックではなくヒープ上に実行コンテキスト(execute_data)を割り当てて、実行の中断・再開に備える 2. 実行コンテキスト保存: 現在の実行状態をヒープ上の新しい領域にコピーし、ジェネレーターオブジェクト内に保存して、ZEND_CALL_GENERATORフラグを設定する 3. 呼び出しフレーム管理: 現在の実行コンテキストを前の実行データに戻し、呼び出し情報に応じて適切なクリーンアップ処理を行ってVMから離脱する ```c ZEND_VM_HANDLER(139, ZEND_GENERATOR_CREATE, ANY, ANY) { zval *return_value = EX(return_value); if (EXPECTED(return_value)) { USE_OPLINE zend_generator *generator; zend_execute_data *gen_execute_data; uint32_t num_args, used_stack, call_info; SAVE_OPLINE(); object_init_ex(return_value, zend_ce_generator); /* * Normally the execute_data is allocated on the VM stack (because it does * not actually do any allocation and thus is faster). For generators * though this behavior would be suboptimal, because the (rather large) * structure would have to be copied back and forth every time execution is * suspended or resumed. That's why for generators the execution context * is allocated on heap. */ num_args = EX_NUM_ARGS(); if (EXPECTED(num_args <= EX(func)->op_array.num_args)) { used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var + EX(func)->op_array.T) * sizeof(zval); gen_execute_data = (zend_execute_data*)emalloc(used_stack); used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var) * sizeof(zval); } else { used_stack = (ZEND_CALL_FRAME_SLOT + num_args + EX(func)->op_array.last_var + EX(func)->op_array.T - EX(func)->op_array.num_args) * sizeof(zval); gen_execute_data = (zend_execute_data*)emalloc(used_stack); } memcpy(gen_execute_data, execute_data, used_stack); /* Save execution context in generator object. */ generator = (zend_generator *) Z_OBJ_P(EX(return_value)); generator->func = gen_execute_data->func; generator->execute_data = gen_execute_data; generator->frozen_call_stack = NULL; generator->execute_fake.opline = NULL; generator->execute_fake.func = NULL; generator->execute_fake.prev_execute_data = NULL; ZVAL_OBJ(&generator->execute_fake.This, (zend_object *) generator); gen_execute_data->opline = opline; /* EX(return_value) keeps pointer to zend_object (not a real zval) */ gen_execute_data->return_value = (zval*)generator; call_info = Z_TYPE_INFO(EX(This)); if ((call_info & Z_TYPE_MASK) == IS_OBJECT && (!(call_info & (ZEND_CALL_CLOSURE|ZEND_CALL_RELEASE_THIS)) /* Bug #72523 */ || UNEXPECTED(zend_execute_ex != execute_ex))) { ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_RELEASE_THIS); Z_ADDREF(gen_execute_data->This); } ZEND_ADD_CALL_FLAG_EX(call_info, (ZEND_CALL_TOP_FUNCTION | ZEND_CALL_ALLOCATED | ZEND_CALL_GENERATOR)); Z_TYPE_INFO(gen_execute_data->This) = call_info; gen_execute_data->prev_execute_data = NULL; call_info = EX_CALL_INFO(); EG(current_execute_data) = EX(prev_execute_data); if (EXPECTED(!(call_info & (ZEND_CALL_TOP|ZEND_CALL_ALLOCATED)))) { EG(vm_stack_top) = (zval*)execute_data; execute_data = EX(prev_execute_data); LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); } else if (EXPECTED(!(call_info & ZEND_CALL_TOP))) { zend_execute_data *old_execute_data = execute_data; execute_data = EX(prev_execute_data); zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); } else { ZEND_VM_RETURN(); } } else { ZEND_VM_DISPATCH_TO_HELPER(zend_leave_helper); } } ``` #### Interface {#interface} php docに記述されている。 - generator: - iterator: ## 得られた結果・所感 {#得られた結果-所感} yieldの知らなかった使い方や実際にphp-srcがどういう実装になっているのか深堀できてよかった。 [Laravel LazyCollection in depth]({{< relref "20250204175231-laravel_lazy_collection_in_depth.md" >}}) の理解がより深まった。 DeepWikiで質問をしながら実際のコードを読むというのは体験が良かった。 [DeepWiki調査メモ]({{< relref "20250518171454-deepwiki_memo.md" >}}) にも書いたとおり、deepwiki-openを使えばprivate repoでも実現できるので積極的に活用していきたい。 ## 今後の展開・検討事項 {#今後の展開-検討事項} 他にもphp-srcの気になる機能のコードリーディングを気軽にやっていきたい。 -------------------------------------------------------------------------------- title: "node2nixの使い方" date: "2025-06-22" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 最近のAIツールの隆盛でnpm packageを使う機会が増えてきた。 MCP実行するpackageの管理や、claude codeのようなpackageの管理をNixなどが具体例として挙げられる。 [@github/copilot-language-serverをNixで扱う時のメモ]({{< relref "20250327190937-usage_github_copilot_language_server.md" >}}) や [mcp-servers-nixを導入した]({{< relref "20250409210655-introduction_mcp_servers_nix.md" >}}) でも `node2nix` を使っていることを書いていたので、 `node2nix` の使い方について後学の為に纏めておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. node2nix周辺調査 {#1-dot-node2nix周辺調査} 公式レポジトリは以下で、「Generate Nix expressions to build NPM packages」というdescriptionがあるとおり、npm packageをnix packageとして扱えるようにしてくれるもの。 日本語解説記事は以下。 - [NixでZenn CLIをビルドする - Zenn](https://zenn.dev/pandaman64/articles/zenn-built-with-nix) - [node2nix - mrsekut-p scrapbox](https://scrapbox.io/mrsekut-p/node2nix) ### 2. node2nixセットアップ {#2-dot-node2nixセットアップ} 自分のnixos-configurationに `node2nix/` というディレクトリを掘った。 次のようにコマンドを叩くと必要なファイルが作成される。 ```console $ nix-shell -p nodePackages.node2nix $ echo '["@github/copilot-language-server", "@anthropic-ai/claude-code", "sitemcp"]' >> node-packages.json $ node2nix -i node-packages.json ``` ### 3. home-managerに組込み {#3-dot-home-managerに組込み} `node2nix` のディレクトリをimportして `home.packages` に登録するだけ。 ```nix let pkgs = import nixpkgs { inherit system; config.allowUnfree = true; overlays = basicOverlay ++ advancedOverlay; }; nodePkgs = pkgs.callPackage ../node2nix { inherit pkgs; }; in n{ home.packages = [ nodePkgs."@anthropic-ai/claude-code" ]; } ``` ### 4. GitHub Actionsで定期的に更新 {#4-dot-github-actionsで定期的に更新} 一部抜粋すると以下。毎日packageの更新するようにしている。 ```yaml name: update on: workflow_dispatch: schedule: - cron: 0 10 * * * jobs: nix-update: name: Update lockfile runs-on: ubuntu-latest steps: - name: Run node2nix run: | cd node2nix/ nix-shell -p nodePackages.node2nix --command "node2nix -i ./node-packages.json -o node-packages.nix" ``` ## 得られた結果・所感 {#得られた結果-所感} AI関連ツールの進化は激しすぎるので継続的に更新する必要がある。 運用しはじめて半年くらい経ったが、特に事故ることもなく極めて安全に更新し続けられているので導入してよかった。 ## 今後の展開・検討事項 {#今後の展開-検討事項} AI関連ツールをもっと簡単に検証してブログに書いていきたい。 `node2nix` だけでなく `hoge2nix` をもっと活用していきたい。 -------------------------------------------------------------------------------- title: "Typstで履歴書と職務経歴書をBuildする" date: "2025-06-19" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 直近履歴書と職務経歴書を書く必要があった。 以前はGoogle Docsやpagesを使って適当に書いていたのだが、これからのAI時代はコードベースで管理できた方がよいだろうということで移行を決意した。 最近Typst推しなのでTypstからPDFをNix Buildで生成できるようにしてみた。 ## 試したこと・やったこと {#試したこと-やったこと} ### 0. アプローチのやり方を考える {#0-dot-アプローチのやり方を考える} 世間一般的には履歴書や職務経歴書の絶対的な規格というものはなく、業界や各事業者によってさまざまな運用がされているらしい。 履歴書にはJIS規格があるが、必ずしも準拠する必要はないらしい。 必要な情報というのはある程度決まっており、それに倣っておくと汎用性の高い履歴書と職務経歴書になるということで世の中転がってるものをひととおり確認した。 今回は次のようにコンテンツとテンプレートを分けて、より管理をしやすいように設計した。 - YAML: コンテンツをKey/Valueで表現する - Typst: YAMLを読み込んでテンプレートからPDFを作成する ### 1. レポジトリ作成 {#1-dot-レポジトリ作成} 現状GitHubのレポジトリや設定をTerraform上で管理しているので、Terraform経由でPrivateレポジトリを作成した。 - [Terraform GitHub Providerを導入した]({{< relref "20250402001222-setup_private_terraform_github_provider.md" >}}) - [private-terraformをHCP Terraformに移行した]({{< relref "20250507230034-use_private_terraform_cloud.md" >}}) また、他の人が使えるようにOSS版も作った。 ### 2. Nix Build周り整備 {#2-dot-nix-build周り整備} fontは日本語を正しく描画してくれるHackgenを選んだ。 fontもNix経由でインストールできるの非常に体験がよい。 flake.nix: ```nix { inputs = { nixpkgs.url = "github:NixOS/nixpkgs"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem ( system: let pkgs = import nixpkgs { inherit system; }; in { devShells.default = pkgs.mkShell { packages = with pkgs; [ typst ]; }; packages.default = pkgs.stdenv.mkDerivation { name = "private-career-cv"; src = ./.; nativeBuildInputs = with pkgs; [ typst hackgen-font ]; buildPhase = '' typst compile main.typ --font-path ${pkgs.hackgen-font}/share/fonts/hackgen-nf ''; installPhase = '' mkdir -p $out cp main.pdf $out/cv.pdf ''; }; } ); } ``` ### 3. 履歴書テンプレート作成 {#3-dot-履歴書テンプレート作成} 次の記事を参考に履歴書を書くTypstを作った。 - [Nikudanngo/typst-ja-resume-template](https://github.com/Nikudanngo/typst-ja-resume-template) - [Typstで履歴書を書く - Qiita](https://qiita.com/Nikudanngo/items/ed9a452b5f63101fb26b) 実際にNix Buildで生成したPDFはここから確認できる。 {{< figure src="/images/A5A59874-BF32-497C-A7F5-C8043C66B8E8.png" >}} ### 4. 職務経歴書テンプレート作成 {#4-dot-職務経歴書テンプレート作成} 次の記事を参考に職務経歴書を書くTypstを作った。 - [Nikudanngo/typst-ja-resume-template](https://github.com/Nikudanngo/typst-ja-resume-template) - [Typstで履歴書を書く - Qiita](https://qiita.com/Nikudanngo/items/ed9a452b5f63101fb26b) - [ShinoharaTa/typst-work-resume](https://github.com/ShinoharaTa/typst-work-resume) - [エンジニアは職務経歴書をTypstでビルドしよう - Zenn](https://zenn.dev/shino3/articles/a0c9eeb0d09c3a) 実際にNix Buildで生成したPDFはここから確認できる。 {{< figure src="/images/EAE12BCE-37A4-4138-B50F-B71A6BD2F7A5.png" >}} ### 5. コンテンツを埋めて提出する {#5-dot-コンテンツを埋めて提出する} YAMLに必要な情報を埋めてBuild結果のPDFを確認しながら添削をする。 - - ## 得られた結果・所感 {#得られた結果-所感} 無事Typstから生成したもので提出ができたし、保守性が一気に向上したので良かった。 今後は定期的にメンテすることによって、いつでも履歴書や職務経歴書を提出できるようにしていきたい。 ## 今後の展開・検討事項 {#今後の展開-検討事項} GitHub Actions上でNix BuildをしてPDFを生成し、Tag Reelaseでその日付のPDFを保存する仕組みを作りたい。 とりあえず作ったはいいものの、Nix Build時にHackGenがちゃんと反映されなくて豆腐になってしまい辛い。 また、Nix Build時に年齢の計算がうまくいかなかったのでこの辺りも調整したい。 ## Backlinks {#backlinks} - [技術イベントのパンフレット記事をTypstでBuildする]({{< relref "20251130140627-build_pamphlet_article_by_typst.md" >}}) -------------------------------------------------------------------------------- title: "React Router v7周辺キャッチアップメモ" date: "2025-06-16" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 普段からWebフロントエンドは書いているものの、2025年6月現在の最新のフロントエンド事情は追えていなかった。 最後にWebフロントエンドを追っていたのが2021年頃だったので3〜4年のビハインドがあった。 2021年頃はNext.jsが最盛期でNext.js一択だった。 仕事の都合で最新のWebフロントエンド事情をキャッチアップする必要に迫られた。 新規プロジェクトに生かせる為にどのPackageを使うのがよいのか、トレンドはどう変化したのか、過去使っていたpackageはどうなったのか、新しい概念はあるのか等を調べたのでメモしておく。 また、将来的にAIに書かせること前提にドキュメントの整備方法についても検討する。 ## 試したこと・やったこと {#試したこと-やったこと} 作業レポジトリは以下。 練習がてら可能な限りAI(Aider + Gemini 2.5 Pro)に書かせたのでコミット数が多い。 ### 0. キャッチアップ方針決め {#0-dot-キャッチアップ方針決め} キャッチアップをする方法で一番よいのは詳しい人に聞くことなので [@ryoppippi](https://x.com/ryoppippi) と [@tomoyaton](https://x.com/tomoyaton) にそれぞれ2〜3時間くらい質問攻めにした。 その結果次のことが分かった。 - Bundleツール - Webpackは遅いしモダンではない - TurbopackやRspackなどWebpack互換のRust製ツールが隆盛 - 今使うならVite - esbuildとrollupとのよい所取り - rollup互換のRust製ツールであるrolldownが隆盛(experimental) - Component Library - 結局Reactが選択され続けている - Vue周りの話は減った - Framework - Next.jsはモダンではない認識 - Webpack依存を剥せなくてBuildが重い - SSGは廃れた - CJS依存 - 2025年6月はReactを使うならReact Router v7を使うのが無難 - Remixと合流し、「モダンReactのデファクトルーティング」へ変化中 - ref. - SSR + Partial Rendering - Partial Renderingはこの記事が分かりやすい - ref. - オススメのディレクトリ構造などを教えてもらった - `react-router typegen` で型情報を出力できるようになって一気にType Safeになったらしい - UI Library - Mantineが使い勝手がよい - Request Library - swrあたりを使っておくのが無難 - 最近はtanstack queryの方が人気らしい - Logging Library - pinoが使い勝手良かったらしい - Testing Library - Lint/Formatter - 依然としてESLint/PrettierがあるけどBiomeが強い - Unit TestはVitestを使うとよい - Jest互換でTypeScript Native - ESMベース - 残りのE2Eやらは都度考える - 場合によってstorybookやらPlaywrightやらを使うとよい - CI/CD - GitHub Actionsで回しちゃうのが楽でよい - Package Manager - npm/yarn/pnpmがあるが、基本的には好きなものを使えばよい - OSSはpnpmを使ってるプロジェクトが多い - Form/Validation Library - conformが使い勝手良くてよいらしい - zod/valibotあたりを使うのば無難らしい - Hosting - SSRするのでWebサーバを用意する必要がある - Cloudflare workerやらAWS ECSやら選択肢が色々ある - Runtime - Node/Deno/Bunがある - Node/DenoはV8、BunはJavaScript Coreを積んでいる - 何も考えたくなければNodeだけど、Bunの方が速度出るかも? ### 1. 技術選定 {#1-dot-技術選定} 今回の練習用レポジトリで実際に試してみる技術は以下。 - react-router v7 - Mantine - Biome - Vite - Vitest - pnpm awesome-yasunoriが参考実装。 - repo: - url: ### 2. 公式ドキュメントを流し読みしてチュートリアルをやる {#2-dot-公式ドキュメントを流し読みしてチュートリアルをやる} - 作業レポジトリ - - 公式ドキュメント - - チュートリアル - チュートリアルはやるだけなので割愛。題材としての出来が良かった。 ### 3. 開発ツール導入 {#3-dot-開発ツール導入} #### 3.1. Biome導入 {#3-dot-1-dot-biome導入} Biomeを導入した。 別プロジェクトでも導入したことがあったので特に困らなかった。 npm scriptに適当に登録した。 ```json "scripts": { "lint": "pnpm biome check app/", "lint:fix": "pnpm biome check --fix --unsafe app/" }, ``` #### 3.2. Vitest導入 {#3-dot-2-dot-vitest導入} 動かすまでに思ったよりも大変だった。 - - React Routerと相性が悪く、テストの場合はif文が必要だった。 #### 3.3. GitHub Actions導入 {#3-dot-3-dot-github-actions導入} 検証用なので適当に導入した。 ```yaml name: CI on: push: jobs: test: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 name: Install pnpm with: version: 10 run_install: true - name: Run vitest run: pnpm run test:coverage - name: Run lint run: pnpm run lint - name: Run typecheck run: pnpm run typecheck - name: Run build run: pnpm run build ``` ### 4. 実開発 {#4-dot-実開発} #### 4.1. ディレクトリ整理 {#4-dot-1-dot-ディレクトリ整理} ディレクトリ構造についてAIから出力しているので詳しくはこちらを参照。 - 責務の分離とファイルの分離を徹底する - `(ts|tsx)` ファイルに対応する `.spec.(ts|tsx)` を必ず作成する ```console [N] ~/g/g/t/r/a/routes/contact (*´ω`*) < nix run nixpkgs#tree . . ├── action.spec.tsx ├── action.tsx ├── components │   ├── ContactActions.spec.tsx │   ├── ContactActions.tsx │   ├── ContactAvatar.spec.tsx │   ├── ContactAvatar.tsx │   ├── ContactHeader.spec.tsx │   ├── ContactHeader.tsx │   ├── ContactNotes.spec.tsx │   ├── ContactNotes.tsx │   ├── ContactTwitter.spec.tsx │   ├── ContactTwitter.tsx │   ├── Favorite.spec.tsx │   └── Favorite.tsx ├── index.ts ├── loader.spec.tsx ├── loader.tsx ├── route.spec.tsx └── route.tsx 2 directories, 19 files ``` #### 4.2. 型記述について {#4-dot-2-dot-型記述について} 「型推論に頼った方がよいだろう」ということになったので、あまり明示的に書いていない。 たとえば次の例だと `export type TLoader = typeof loader;` でexportしてる。 ```typescript import { getContact } from "../../data"; import type { Route } from "./+types"; export async function loader({ params }: Route.LoaderArgs) { const contact = await getContact(params.contactId); if (!contact) { throw new Response("Not Found", { status: 404 }); } return { contact }; } export type TLoader = typeof loader; ``` 利用する側ではこんな感じになる。 ```typescript import type { TLoader } from "./loader"; const { contact } = useLoaderData(); ``` #### 4.3. Vitest記述 {#4-dot-3-dot-vitest記述} AIに書かせて手直しして書いてみた。 `$ pnpm vitest` でvitestでfile changeをwatchし続けてテストを回せたので体験が良かった。 以下は実際に書かせた例。 - component - container component - - presentational component - - loader - - action - Vitestはカバレッジを簡単に出力できるのもよい。 ```console $ pnpm run test:coverage Test Files 27 passed (27) Tests 88 passed (88) Start at 15:31:38 Duration 12.36s (transform 307ms, setup 3.82s, collect 16.17s, tests 1.28s, environment 15.01s, prepare 2.51s) % Coverage report from v8 --------------------------------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s --------------------------------|---------|----------|---------|---------|------------------- All files | 97.1 | 85.26 | 94.11 | 97.1 | app/layouts/sidebar | 91.66 | 57.14 | 66.66 | 91.66 | layout.tsx | 90.56 | 40 | 50 | 90.56 | 27-31 loader.tsx | 100 | 100 | 100 | 100 | app/layouts/sidebar/components | 100 | 83.33 | 100 | 100 | ContactNavList.tsx | 100 | 75 | 100 | 100 | 19,21 NewContactButton.tsx | 100 | 100 | 100 | 100 | SearchFormComponent.tsx | 100 | 100 | 100 | 100 | SidebarHeader.tsx | 100 | 100 | 100 | 100 | app/root | 100 | 100 | 100 | 100 | action.ts | 100 | 100 | 100 | 100 | loader.ts | 100 | 100 | 100 | 100 | app/root/components | 100 | 92.3 | 100 | 100 | App.tsx | 100 | 100 | 100 | 100 | ErrorBoundary.tsx | 100 | 90 | 100 | 100 | 33 HydrateFallback.tsx | 100 | 100 | 100 | 100 | Layout.tsx | 100 | 100 | 100 | 100 | app/routes/about | 100 | 100 | 100 | 100 | route.tsx | 100 | 100 | 100 | 100 | app/routes/contact | 84.78 | 66.66 | 75 | 84.78 | action.tsx | 100 | 100 | 100 | 100 | loader.tsx | 100 | 100 | 100 | 100 | route.tsx | 79.41 | 33.33 | 50 | 79.41 | 13-14,19-23 app/routes/contact/components | 100 | 100 | 100 | 100 | ContactActions.tsx | 100 | 100 | 100 | 100 | ContactAvatar.tsx | 100 | 100 | 100 | 100 | ContactHeader.tsx | 100 | 100 | 100 | 100 | ContactNotes.tsx | 100 | 100 | 100 | 100 | ContactTwitter.tsx | 100 | 100 | 100 | 100 | Favorite.tsx | 100 | 100 | 100 | 100 | app/routes/destroy-contact | 100 | 100 | 100 | 100 | action.tsx | 100 | 100 | 100 | 100 | app/routes/edit-contact | 100 | 54.54 | 100 | 100 | action.tsx | 100 | 100 | 100 | 100 | loader.tsx | 100 | 100 | 100 | 100 | route.tsx | 100 | 28.57 | 100 | 100 | 17-37 app/routes/home | 100 | 100 | 100 | 100 | route.tsx | 100 | 100 | 100 | 100 | testing-utils | 100 | 100 | 100 | 100 | index.ts | 100 | 100 | 100 | 100 | render.tsx | 100 | 100 | 100 | 100 | --------------------------------|---------|----------|---------|---------|------------------- ``` GUIでも確認が取れる。 {{< figure src="/images/8E8A4CC8-DB1A-455D-B0A8-E27B46932786.png" >}} #### 4.4. Mantine導入 {#4-dot-4-dot-mantine導入} Getting Startedを読んで導入後、既存のコンポーネントをAIに書き直させた。 AIが思った以上に書いてくれなかったので手で直したのと、Vitestのテストが落ちまくったので気合で直した。 Componentのテストは `role` でやるのではなく、 `test-id` でやった方がやりやすかった。 VitestでMantine Componentをテストする時に `render` する度に `` で囲う必要があったので自作した。 ```typescript import { render as testingLibraryRender } from '@testing-library/react'; import { MantineProvider } from '@mantine/core'; export function render(ui: React.ReactNode) { return testingLibraryRender(<>{ui}, { wrapper: ({ children }: { children: React.ReactNode }) => ( {children} ), }); } ``` ### 5. AI用ドキュメント {#5-dot-ai用ドキュメント} #### 5.1. ドキュメント生成用ドキュメントを用意 {#5-dot-1-dot-ドキュメント生成用ドキュメントを用意} ChatGPTにベースのプロンプトを用意させて手直しした。 #### 5.2. プロジェクト内にドキュメントを配備 {#5-dot-2-dot-プロジェクト内にドキュメントを配備} `docs/` に出力させて都度手直しした。 ## 得られた結果・所感 {#得られた結果-所感} 質問時間5〜6時間、検証時間15時間のざっくり20時間くらい調査して大体把握できたのが良かった。 File Base Routingや型がうまくつけられなかったNext.jsよりも圧倒的に書きやすくなったなーという感覚で進化を感じた。 VitestやMantineでそれなりに詰まったので、小さく試せるSandbox環境でまず検証してみるのが重要だなと改めて感じた。 UIライブラリはいつも悩みの種だったのでMantineの使い勝手の良さに感動した。 今後toBサービスではこれを採用していきたい。 Vitest周りがうまく動かなくて想像以上に苦労したが、初期セットアップのときだけ詰まるような内容なので喉元過ぎた。 ## 今後の展開・検討事項 {#今後の展開-検討事項} conformやzodやpinoはまだ試しきれていないので追加で調査する。 実際にプロダクション導入してみてAIにコードを書かせてみて知見を溜めたい。 AIコーディング時代ではリグレッションテストの重要性が増しているので色々試してみたい。 -------------------------------------------------------------------------------- title: "home-managerで自作moduleを作る" date: "2025-06-14" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} home-managerには沢山のmoduleが提供されている。 home-manager moduleは自分でも作成可能なので後学のためにメモしておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 0. なぜやるのかの整理 {#0-dot-なぜやるのかの整理} 大きく分けて3パターンが考えられる。 1. home-managerで自作moduleを作る 2. ファイルを作成してhome-managerでsymbolic linkを貼る 3. Nix管理外でメンテする 1とそれ以外で次のようなメリット/デメリットが考えられる。 可能な限りNix(Nix式)に寄せたいと考えているので今回採用することにした。 #### Pros {#pros} | 観点 | 内容 | |---------|------------------------------------| | フォーマット抽象化 | 多様なフォーマットを意識せずに Nix 式で統一管理できる | | IDE支援 | Nix LSP の補完・型チェックの恩恵を受けられる | | 統一性 | \`nixpkgs\` の差し替えなど、構成記述の一貫性・統一性が上がる | | 再利用性 | モジュールとして切り出すことで複数環境で再利用しやすくなる | | ライフサイクル | rebuild switchのライフサイクルで反映できる | | 変数埋込 | Nixから変数を代入できる | #### Cons {#cons} | 観点 | 内容 | |------|----------------------------------------| | 表現力の制限 | tmux.confのようなフォーマットは文字列での管理が必要 | | LSPの範囲 | LSP は Nix に対してしか効かず、生成対象の構文チェックは別途必要 | | 保守性 | 作り込むと構造が複雑化し、メンテナンスコストが上がる可能性がある | | 依存性 | home-manager に構成が強く依存し、他ツールへの移行が難しくなることも | | デバッグ性 | モジュール解決や展開の追跡が難しく、デバッグの難易度が多少上がる | ### 1. 自作home-manager moduleを作成する {#1-dot-自作home-manager-moduleを作成する} home-mnaagerのobjectでimportするだけで使える。 #### 1.1. tigの場合 {#1-dot-1-dot-tigの場合} `~/.tigrc` に文字列をそのまま出力する運用にしている。 [home-manager/modules/tig/default.nix](https://github.com/takeokunn/nixos-configuration/blob/e2cf7d9df556a56e242b3f44b0957884cd6191dd/home-manager/modules/tig/default.nix) ```nix { pkgs, lib, config, ... }: let cfg = config.programs.tig; in with lib; { options.programs.tig = { enable = mkEnableOption "Text-mode interface for git"; package = mkPackageOption pkgs "tig" { }; config = mkOption { type = types.lines; default = ""; }; }; config = mkIf cfg.enable { home.packages = [ cfg.package ]; home.file.".tigrc".text = cfg.config; }; } ``` #### 1.2. lnavの場合 {#1-dot-2-dot-lnavの場合} `~/.config/lnav/config.json` にNix式をJsonに変換して出力するようにしている。 [home-manager/modules/lnav/default.nix](https://github.com/takeokunn/nixos-configuration/blob/e2cf7d9df556a56e242b3f44b0957884cd6191dd/home-manager/modules/lnav/default.nix) ```nix { pkgs, lib, config, ... }: let cfg = config.programs.lnav; jsonFormat = pkgs.formats.json { }; in with lib; { options.programs.lnav = { enable = mkEnableOption "Log file navigator"; package = mkPackageOption pkgs "lnav" { }; config = mkOption { type = jsonFormat.type; }; }; config = mkIf cfg.enable { home.packages = [ cfg.package ]; xdg.configFile = { "lnav/config.json".source = jsonFormat.generate "config.json" cfg.config; }; }; } ``` ### 2. 自作home-manager moduleを利用する {#2-dot-自作home-manager-moduleを利用する} #### 2.1. tigの場合 {#2-dot-1-dot-tigの場合} 通常とおり `programs.tig` に記述すればよい。(以下抜粋) [home-manager/programs/tig/default.nix](https://github.com/takeokunn/nixos-configuration/blob/e2cf7d9df556a56e242b3f44b0957884cd6191dd/home-manager/programs/tig/default.nix) ```nix { programs.tig = { enable = true; config = '' # config set main-view = id date author:email-user commit-title:graph=yes,refs=yes set blame-view = date:default author:email-user id:yes,color line-number:yes,interval=1 text ''; }; } ``` #### 2.2. lnavの場合 {#2-dot-2-dot-lnavの場合} 通常とおり `programs.lnav` に記述すればよい。(以下抜粋) [home-manager/programs/lnav/default.nix](https://github.com/takeokunn/nixos-configuration/blob/e2cf7d9df556a56e242b3f44b0957884cd6191dd/home-manager/programs/lnav/default.nix) ```nix { pkgs }: { programs.lnav = { enable = true; package = pkgs.lnav; config = { ui.theme = "dracula"; format-repos = [ "https://github.com/hagfelsh/lnav_formats.git" "https://github.com/PaulWay/lnav-formats.git" "https://github.com/penntaylor/lnav-ruby-logger-format.git" "https://github.com/aspiers/lnav-formats.git" ]; }; }; } ``` ## 得られた結果・所感 {#得られた結果-所感} Nix式で一元管理できるようになって統一的な記述ができるようになって嬉しい。 また、home-managerの仕組みの理解が進んで個人的には大満足。 ## 今後の展開・検討事項 {#今後の展開-検討事項} 手元の運用で安定してきたらhome-manager本体にPRを出していきたい。 -------------------------------------------------------------------------------- title: "AIを駆使してTypstスライドを生成する" date: "2025-06-10" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} [Typstとpdfpcでプレゼン機能を実現する]({{< relref "20250608145128-presenting_with_typst_pdfpc.md" >}}) のNext Actionに次のようなことを書いた。 > 登壇の場数を踏んでより洗練させていきたい。 > 登壇周りのすべてがプレーンテキスト(Org)で管理できるようになったので、次はAIによるスライド生成にチャレンジしたい。 実際に練習がてら登壇してみたかったので、[第177回 PHP勉強会@東京](https://phpstudy.connpass.com/event/357699/) で「Typstで登壇できるようになったので練習させてくれ」というテーマでLTをすることにした。 そこで今回は実際にAIにスライドの目次案からTypstを生成させてホスティングする所までやってみた。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. AIでプロンプトを作る {#1-dot-aiでプロンプトを作る} #### プロンプト {#プロンプト} 関連するブログをリストアップしてMarkdownで目次を生成させてみた。 ```markdown 「typstで登壇できるようになったので練習させてくれ」というタイトルでLTしようと考えています。 登壇資料のアウトラインを作りたいです。 以下の記事を読んで起承転結でMarkdownで纏めてください。 - Typstとpdfpcでプレゼン機能を実現する - https://www.takeokunn.org/posts/fleeting/20250608145128-presenting_with_typst_pdfpc/ - Typst自作テーマで登壇スライドを作成した - https://www.takeokunn.org/posts/fleeting/20250601214129-create_slide_by_typst_self_theme/ - Typstテーマを自作した - https://www.takeokunn.org/posts/fleeting/20250506220914-create_typst_slide_theme/ - NixでTypstをBuildしGitHub Pagesでホスティングする - https://www.takeokunn.org/posts/fleeting/20250126114218-build_and_deploy_typst_using_nix/ ``` #### アウトライン例 {#アウトライン例} 以下は対話しながらGeminiから生成させたものを改変した。 ```markdown # Typstで登壇できるようになったので練習させてくれ ## 全体構成 ### きっかけと違和感 - かつてはGoogle Slidesは最高の相棒だった - 画像位置を調整するのが簡単 - 過去に作ったスライド資産がある - 外部への共有も簡単 - スライド作成の流れ - Emacsでアウトラインを書く - 過去スライドをコピペしてくる - アウトラインに沿ってスライドを作る - スピーカーノートを用意する - 心に刺さった"小さなトゲ - プレーンテキストで管理できない - AIによる自動生成できない - Emacsから編集できない - etc... - 違和感の正体 = エンジニアとしての"業"(カルマ) - バージョン管理が恋しい - 再利用性が低い (No DRY!) - 自動化できない - 僕らは、スライドという成果物も“コード”として管理したい生き物なんだ! ### Typstとの出会いと感動 - そんな僕が出会ったのが"Typst" - まさに「プレゼン資料も構造化・宣言的に書ける」言語 - 探し求めていた理想がそこにあった - 最初の感動ポイント(="トゲ"が抜けていく快感) - コードでスライドが書ける → Emacsから離れなくていい! - アウトライン作成からスライド執筆まで、すべてが愛用のエディタで完結 - Gitで管理できる → 変更履歴はすべてお見通し! - テーマが自作できる → DRY原則、スライドにも! - コードハイライトが美しい → エンジニアとして一番見せたいものが輝く! - Typstスライドを支える技術(しくみ) - Polylux:Typstのためのスライドエンジン - codelst:コードを美しく見せるスタイリスト - Nix:すべてを固める魔法の環境 ### 実用に耐えるまでの道のり - しかし、現実は甘くなかった - 理想の環境はすぐそこに見えたが、やってみると色々な壁にぶつかる - 「言うは易く行うは難し」を実感する日々 - やってみて困ったこと(理想と現実のギャップ) - テーマの調整が超むずい - polyluxやcodelstのドキュメントとひたすらにらめっこ - 絵文字: Macでは出るのにCI(Linux)でビルドすると豆腐(□)になる! - フォント設定: この日本語フォントを使いたいだけなのに… - 画像の配置: ドラッグ&ドロップの偉大さを思い知る - それでも続けた理由(Typstの魅力に立ち返る) - 記法が明快で、根本的な書き心地は最高だった - スライド構成が自分の思考にフィットした - 「構造から考え、中身を埋める」スタイルが気持ちよかった - 補助ツールとの統合(そして"最強"の環境へ) - Typst単体ではなく、エコシステム全体で課題を解決していく - pdfpc:ついに「登壇」できるようになった瞬間 - GitHub Actions:完全なる自動化の世界へ - Hugo:発表資料を"資産"にする ### Typst登壇はこう変えた - Typstを手に入れた結果、登壇準備のすべてが変わった - Before/Afterで振り返る、スライド制作プロセスの変化 - 「きっかけと違和感」で感じていた違和感がすべて解消されていく - 登壇準備がこう変わった! - 迷わず構成から入れる - Before:まずGoogle Slidesを開き、どのテーマにしようか悩む… - After:まずEmacsでアウトライン(構造)を書くことに集中できる - 見た目を気にしすぎなくていい - Before:テキストボックスの位置、フォントサイズ…ピクセル単位の調整地獄 - After:自作テーマを適用すればOK。「見た目は後からついてくる」安心感 - 再利用性・再現性が圧倒的に高い - Before:過去スライドからコピペ。コピペミスやうっかり編集しちゃったなどヒューマンエラーが頻発。 - After:過去の資産はGitリポジトリに。Nixのおかげで誰でも同じPDFを生成 - Typstで登壇する未来 - 自分だけのテーマを育てる文化 - 発表のたびにテーマを少しずつ改善していく。自分だけの"刀"を研ぐ感覚 - コード × 構造 × 登壇の三位一体 - Slide as Codeの世界へようこそ - まとめとメッセージ - Google Slidesへの違和感から始まり、困難を乗り越え、最強の環境を手に入れました - 「一度書いてみれば、この快感が分かる」 - 発表資料も"成果物"にしたい、すべてのエンジニアへ - その場限りの資料ではなく、再利用可能な“資産”を作りたい人は、ぜひTypstへ! ``` ### 2. Gemini 2.5 Proに実装させる {#2-dot-gemini-2-dot-5-proに実装させる} 作業指示を書いて8割くらいのクオリティでAIに実装させる。 ```markdown 作業指示 1. 下記のMarkdownを読み込む 2. typst/org/nix-meetup-3-my-nix-usecase/article.org のコードを読む 3. typst/org/typst-presentation-practice/article.org にOrgベースでTypstスライドを作成する - typst/org/nix-meetup-3-my-nix-usecase/article.org を可能な限り参考にする 4. typst/flake.nix に typst-presentation-practice を追記する 5. .github/workflows/main.yml に追記する ---- <<アウトラインのMarkdownを貼る>> ``` ### 3. スライドのクオリティを上げる {#3-dot-スライドのクオリティを上げる} 次の対応をした。 - 自己紹介やアジェンダなどテンプレ追加 - 画像を追加 - 生成されたTypstを手直し - 文言やスピーカーノートを追加修正 ### 4. git pushする {#4-dot-git-pushする} [NixでTypstをBuildしGitHub Pagesでホスティングする]({{< relref "20250126114218-build_and_deploy_typst_using_nix.md" >}}) にもあるとおり、 `git push` するだけで公開される。 ## 得られた結果・所感 {#得られた結果-所感} 無事スライドができてホスティングできた。 {{< figure src="/images/2E2BE49E-2CFB-4FC6-B25A-237B90C5D5C8.png" >}} 実際のコードはこちら。 ベースのプロンプトやTypst力が上がればLTスライドの生成まで1時間あれば終わることが分かった。 ## 今後の展開・検討事項 {#今後の展開-検討事項} 実際の登壇の感想は後日記述する。 ## Backlinks {#backlinks} - [技術イベントのパンフレット記事をTypstでBuildする]({{< relref "20251130140627-build_pamphlet_article_by_typst.md" >}}) -------------------------------------------------------------------------------- title: "EventBridge Ruleデバッグ方法メモ" date: "2025-06-08" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 業務をしていると、「EventBridgeでメタ情報を付与してSQSに流す」といった要件に出くわす機会がある。 e.g. [AWS SQSとLaravelで大規模トラフィックを捌く - phpcon新潟](https://docs.google.com/presentation/d/1_vOdQO0SY671SRQpnpEz1xvVyUy-Vzl_Dk4Ph6ujy84/edit?slide=id.p#slide=id.p) Event Busが一次受けをして複数のEventBridge Ruleでマッチしたものに情報を付与して次に流す、といった挙動になっている。 - [EventBridge - AWS公式ドキュメント](https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-what-is.html) - [Amazon EventBridge ルール](https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-rules.html) - [Amazon EventBridge イベントパターン](https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-event-patterns.html) EventBridge Ruleのデバッグをするには[Amazon EventBridge でイベントに応答するルールの作成](https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-create-rule.html) に書いてあるとおりにやればいいのだが、AWSのドキュメントは初学者から見たら分かりにくく混乱した。 今回は後学の為に画像付きでメモしておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. AWS consoleから対象のEventBridge Rulesを開く {#1-dot-aws-consoleから対象のeventbridge-rulesを開く} 対象のEventBusを選択して `Create Rule` を押す。 {{< figure src="/images/448BDAE9-E4D3-45BF-84EA-8C954508FED8.png" >}} ### 2. 「Build event pattern」を開く {#2-dot-build-event-pattern-を開く} Define rule detailのnameは適当に入れる。 ### 3. Sample eventでJsonを入れる {#3-dot-sample-eventでjsonを入れる} 「Enter my own」を選択して「Misc > Webhook Sample」にあるようなテスト用のJsonを入れる。 {{< figure src="/images/8CDED877-B8C3-4432-BA86-F867C5914C31.png" >}} 以下はShopifyの `inventory_items/create` の実データをマスクしたもの。 ```json { "version": "0", "id": "xxx-xxx-xx-xxx", "detail-type": "shopifyWebhook", "source": "aws.partner/shopify.com/000000/test", "account": "000000000000", "time": "2025-03-01T06:29:19Z", "region": "ap-northeast-1", "resources": [], "detail": { "payload": { "id": 100000000000, "sku": "", "created_at": "2025-03-01T01:29:17-05:00", "updated_at": "2025-03-01T01:29:17-05:00", "requires_shipping": true, "cost": null, "country_code_of_origin": null, "province_code_of_origin": null, "harmonized_system_code": "111111", "tracked": true, "country_harmonized_system_codes": [], "admin_graphql_api_id": "gid://shopify/InventoryItem/0000001" }, "metadata": { "Content-Type": "application/json", "X-Shopify-Topic": "inventory_items/create", "X-Shopify-Shop-Domain": "test.myshopify.com", "X-Shopify-Hmac-SHA256": "xxxx", "X-Shopify-Webhook-Id": "xxx-xxx-xxx-xxx-xxxx", "X-Shopify-API-Version": "2024-10", "X-Shopify-Triggered-At": "2025-03-01T06:29:17.989779121Z" } } } ``` ### 4. Event patternを入力してテストをする {#4-dot-event-patternを入力してテストをする} 検証したいEventBridge Ruleを貼り付けて 「Test pattern」 をすると検証できる。 {{< figure src="/images/86DD23E0-7577-47D1-BC1E-84930BA9DBB3.png" >}} ## 得られた結果・所感 {#得られた結果-所感} AWS ConsoleからEventBridge Ruleの検証を気軽にできるようになった。 suffix-matchingの仕様が直感から反するのでメモをしておく。 次のサンプルは FileName のsuffixが `.png` のものだけひっかかるようなRule。 ```json { "FileName": [ { "suffix": ".png" } ] } ``` suffixはstringのみを受け付けておりintegerは許可されていない。 なので、 `id: 12345` のようなinteger値が飛んできた時に次のRuleは発火しない。 ```json { "id": [ { "suffix": "5" } ] } ``` ## 今後の展開・検討事項 {#今後の展開-検討事項} CIでテストする方法がないか追加で調査する。 -------------------------------------------------------------------------------- title: "Typstとpdfpcでプレゼン機能を実現する" date: "2025-06-08" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} Typstでスライドを作るフローがだいぶ固まってきた。 - [NixでTypstをBuildしGitHub Pagesでホスティングする]({{< relref "20250126114218-build_and_deploy_typst_using_nix.md" >}}) - [Typstテーマを自作した]({{< relref "20250506220914-create_typst_slide_theme.md" >}}) - [Typst自作テーマで登壇スライドを作成した]({{< relref "20250601214129-create_slide_by_typst_self_theme.md" >}}) 過去に普段資料はGoogle Slideで作成していたのだが、次の理由でGoogle Slideを使っていた。 例: [AWS SQSとLaravelで大規模トラフィックを捌く - phpcon新潟](https://docs.google.com/presentation/d/1_vOdQO0SY671SRQpnpEz1xvVyUy-Vzl_Dk4Ph6ujy84/edit?slide=id.p#slide=id.p) - Google Driveで管理できる - 自己紹介部分など過去のスライドを流用できる - 文字や画像の配置に自由度がある - プレゼンター表示ができる - スピーカーノートを別画面で確認できる - 経過時間の計測ができる 管理方法やスライドに関するものはTypst力が上がればカバーできるが、プレゼンター表示に関しては何か対策を講じなければ代替できない。 今回は実際に登壇に耐えうるツールの選定と運用の検討をしたのでメモしておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 0. ツールの選定 {#0-dot-ツールの選定} 自分がプレゼンテーションモードに求めている機能をリストアップすると以下。 - MUST - 登壇スライドとは別にスピーカーノートを別画面で確認できる - 経過時間の計測ができる - Typst内 or Orgファイル内でスピーカーノートを管理できる - SHOULD - 次スライドの表示 - 全体のスライド表示 - MAY - ホワイトボード/ペンツール機能 - オフラインで機能する - 動画やスライドアニメーションなどの制御 Typstから生成したPDFをそのままプレゼン資料として表示できるツールは無数にある。 - [Keynote](https://www.apple.com/jp/keynote/) - [PowerPoint](https://www.microsoft.com/ja-jp/microsoft-365/powerpoint) - [Pympress](https://github.com/Cimbali/pympress) - etc.. [Typstテーマを自作した]({{< relref "20250506220914-create_typst_slide_theme.md" >}}) にも書いたとおり、Typstのベーステーマに [polylux](https://polylux.dev/book/polylux.html) を利用している。 polyluxのドキュメントを読んでいるとpdfpcと繋ぎ込む記述があり、これを用いるとスピーカーノートを実現できることが分かる。 可能な限り公式がサポートしているものを使う方が筋がよい、また要件を十分に満たしていることが確認が取れたので今回は[pdfpc](https://pdfpc.github.io/)を選択した。 ### 1. Typstとpdfpcとの繋ぎ込み {#1-dot-typstとpdfpcとの繋ぎ込み} pdfpc integrationは [polylux-typ/polylux](https://github.com/polylux-typ/polylux) の [src/toolbox/pdfpc.typ](https://github.com/polylux-typ/polylux/blob/c58e89c890c397eb300e1a144e6c88b5b2851399/src/toolbox/pdfpc.typ) にある。 使い方は簡単で、次のように記述するだけでよい。 ```typst #import "@preview/polylux:0.4.0": * #slide[ #toolbox.pdfpc.speaker-note("This is a note that only the speaker will see.") ] ``` [polylux2pdfpc](https://search.nixos.org/packages?channel=25.05&show=polylux2pdfpc&from=0&size=50&sort=relevance&type=packages&query=polylux2pdfpc)を使ってpdfpcファイルを生成する。 ```bash # PDFを生成 $ typst compile foo.typ # pdfpcを生成 $ polylux2pdfpc foo.typ ``` `foo.pdfpc` が生成される。 ```json { "pdfpcFormat": 2, "disableMarkdown": false, "pages": [ { "idx": 0, "label": 1, "overlay": 0, "forcedOverlay": false, "hidden": false, "note": "This is a note that only the speaker will see." }, { "idx": 1, "label": 2, "overlay": 0, "forcedOverlay": false, "hidden": false }, { "idx": 2, "label": 3, "overlay": 0, "forcedOverlay": false, "hidden": false }, { "idx": 3, "label": 4, "overlay": 0, "forcedOverlay": false, "hidden": false }, { "idx": 4, "label": 5, "overlay": 0, "forcedOverlay": false, "hidden": false }, { "idx": 5, "label": 6, "overlay": 0, "forcedOverlay": false, "hidden": false }, { "idx": 7, "label": 7, "overlay": 0, "forcedOverlay": false, "hidden": false } ] } ``` なお私はNixユーザなのでflake.nixで記述している。 ### 2. pdfpcインストール {#2-dot-pdfpcインストール} pdfpcはnixpkgsにあるのでインストールが簡単だが、手元のM1 Macだと次のようなエラーが出て入らなかった。 ```console error: Package ‘webkitgtk-2.48.2+abi=4.1’ in /nix/store/dzwwydggypqg4p8r6if5zdaqyzhsxjkd-source/pkgs/development/libraries/webkitgtk/default.nix:254 is marked as broken, refusing to evaluate. ``` homebrewにもパッケージがあるのでこちらから入れた。 次のようにコマンドを叩いて生成したpdfpcファイルと共にpdfを指定するとビューワーが起動した。 複数モニタにしてから起動すると手元PCには管理側の画像、外部モニターにはPDFが全画面で表示された。 ```console $ /opt/homebrew/bin/pdfpc result/foo.pdf -R result/foo.pdfpc ``` {{< figure src="/images/10CAB95D-C307-46B7-BDBA-29E0D9085FA3.png" >}} ### 3. pdfpcの使い方確認 {#3-dot-pdfpcの使い方確認} [マスタリングpdfpc - Qiita](https://qiita.com/s417-lama/items/9f332abf55710b4688b5) に操作方法が詳細に記述してある。 手元のM1 Macの場合は微妙にキーバインドが違うのか期待とおり動いてくれなかったので要練習。 - `j/k` で次のスライドに移動できない - ポインタやお絵描きのキーバインドが異なる `?` で画面上でkeybindを確認できるようだ。 {{< figure src="/images/DA6E03AC-6232-4B94-82C0-ABA25F6ED732.png" >}} ## 得られた結果・所感 {#得られた結果-所感} 当初の目標であるGoogle Slideの脱却が今回の対応で可能になったのは個人的に大きい。 pdfpcの使い方に関しては登壇を通じて練習していきたい。 ## 今後の展開・検討事項 {#今後の展開-検討事項} 登壇の場数を踏んでより洗練させていきたい。 登壇周りのすべてがプレーンテキスト(Org)で管理できるようになったので、次はAIによるスライド生成にチャレンジしたい。 ## Backlinks {#backlinks} - [AIを駆使してTypstスライドを生成する]({{< relref "20250610215948-ai_powered_typst_slide_generation.md" >}}) -------------------------------------------------------------------------------- title: "Typst自作テーマで登壇スライドを作成した" date: "2025-06-01" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} [Nix meetup #3 大阪](https://nix-ja.connpass.com/event/353532/) で大阪への新幹線の中で登壇スライドを作成した。 今まではGoogle Slideでスライドを作成していたが、今回は [Typstテーマを自作した]({{< relref "20250506220914-create_typst_slide_theme.md" >}}) にもあるとおり自作テーマで初の登壇仕様を作成してみた。 スライドは作ったものの、時間の関係で登壇できなかったので別の機会で流用する。 ## 試したこと・やったこと {#試したこと-やったこと} ### 「オープニングスライド」の場合 {#オープニングスライド-の場合} {{< figure src="/images/D81466CB-B613-45D1-8A75-4D4FFD3F9DB1.png" >}} ### 「Agenda」の場合 {#agenda-の場合} {{< figure src="/images/56A9F6DF-679D-4299-AD9A-5FA68F7B16E5.png" >}} `#outline` で自動で取得できる。 ```typst #slide[ = Agenda #outline ] ``` {{< figure src="/images/27AD5F73-C5C0-46FA-BD3B-69DB46F770EA.png" >}} `#new-section` が対象。 ```typst #new-section[自己紹介] #new-section[今回話したいこと] #new-section[事例紹介] #new-section[今後やりたいこと] ``` ### 「自己紹介スライド」の場合 {#自己紹介スライド-の場合} 画像は `#place` で右上に表示。 ```typst #slide[ #place( top + right, image("./images/icon.jpeg", width: 100pt) ) = 自己紹介 - たけてぃ \@takeokunn - Nix Meetup 共同主催 - Nix歴ようやく1年経った - 仕事ではPHPコミュニティ所属 - 個人ではテキストエディタコミュニティ所属 ] ``` ### 「ソースコード」の場合 {#ソースコード-の場合} {{< figure src="/images/5C591DBD-F3EF-46EA-B922-D9E04D18B4EE.png" >}} コードは `#sourcecode` でシンタックスハイライトされる。 ````typst #slide[ = devenv #sourcecode(frame: none)[```nix { pkgs, config, inputs, ... }: { cachix.enable = false; dotenv.disableHint = true; packages = with pkgs; [ typescript typescript-language-server ]; languages.javascript = { enable = true; package = pkgs.nodejs_22; }; } ```] ] ```` ### 「OGP画像」の場合 {#ogp画像-の場合} {{< figure src="/images/3EC43E62-8425-4B75-805A-820EF84BE986.png" >}} `#place` と `image` 関数だけでよい。 ````typst #slide[ = blog #place(image("./images/blog.png", fit: "contain", width: 100%, height: 100%)) ] ```` ### 「箇条書き」の場合 {#箇条書き-の場合} {{< figure src="/images/B7A532B7-4A07-445A-8C3C-0FF5B227D6FF.png" >}} 通常とおり箇条書きすればよい。 ### 「終わりに」の場合 {#終わりに-の場合} {{< figure src="/images/0D31D178-3B7F-48C3-AD33-204775E11CE9.png" >}} 中央に大きめの文字を配置するパターン。 `#show: focus` で実現できる。 ````typst #slide[ #show: focus ご静聴ありがとうございました ] ```` ## 得られた結果・所感 {#得られた結果-所感} 見栄えのよいスライドをorg-modeとTypstで簡単に作成できた。 [NixでTypstをBuildしGitHub Pagesでホスティングする]({{< relref "20250126114218-build_and_deploy_typst_using_nix.md" >}}) にもあるとおり、 `git push` したら数分後にGitHub Pagesからアクセスできるようになるので体験が非常によい。 - pdf - - org - ## 今後の展開・検討事項 {#今後の展開-検討事項} 簡易的なレイアウトは実現できるようになったので、レイアウトの幅をもっと柔軟にして質の高いスライドを作成できるようにしたい。 ## Backlinks {#backlinks} - [Typstとpdfpcでプレゼン機能を実現する]({{< relref "20250608145128-presenting_with_typst_pdfpc.md" >}}) -------------------------------------------------------------------------------- title: "keyoxideセットアップ" date: "2025-05-28" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 友人のこまもか君(@Comamoca_)がTwitterでなりすましをされていた。 自分はまだされたことがないが、将来的になりすまされた時の為に今のうちに手を打っておこうということで今回対応した。 ## 試したこと・やったこと {#試したこと-やったこと} ### 0. 方針を決める {#0-dot-方針を決める} インターネットでなりすましではないことを証明するのは根本的に難しい。 ChatGPTによると、本人性を証明するためのアプローチは次の3つが上げられる。 - A. 他媒体連携型証明(クロスメディア検証) - B. 動画・写真による自己証明 - C. 本人だけが知る事実・履歴を提示 今回の要件としては、自分のことをあまり知らない不特定多数から見て自己をどう証明するのかというものがある。 私の場合Twitter/GitHub/ブログがメインなので、これらを証明する手段が求められている。(A. 他媒体連携型証明) [GPG KeyのYubiKey運用をはじめた]({{< relref "20250514155803-yubikey_gpg_operation.md" >}}) にも書いたとおり、可能な限りGPGに寄せたいと考えている。 自分のソーシャルアカウントや公開鍵との関連性を証明するサービスはいくつかあるようだ。 - Keybase - Keyoxide - Sigstore - etc.. 当初keybaseを採用しようとしていたが、自分にとってSNS機能は不要だったり、Zoomが買収してから開発が停滞気味という噂を聞き、Keyoxideを採用することにした。 また友人のnatsukiumも利用していたので決断の後押しになった。 ### 1. Keyoxideに認識させる {#1-dot-keyoxideに認識させる} 基本的にはドキュメントを参考に上から順に行えばよい。 [GPG KeyのYubiKey運用をはじめた]({{< relref "20250514155803-yubikey_gpg_operation.md" >}}) ですでにSigned Keyは持っていたのでGPG Key生成手順はスキップできた。 次のコマンドで `public.asc` を生成して管理画面からアップロードする。 ```console $ gpg --armor --output public.asc --export ``` keyoxide側が勝手にopenpgpを参照してページを作ってくれる。 ### 2. Twitter紐付け作業 {#2-dot-twitter紐付け作業} こちらを参考に対応する。 `openpgp4fpr:` を含めたツイートをする。 ツイートのリンクを `notation` で付与する。 ```console $ gpg --edit-key > notation @Enter the notation: proof@ariadne.id=https://twitter.com/takeokunn/status/1927387715441414384 > save ``` 次のコマンドで `public.asc` を生成して管理画面からアップロードする。 ```console $ gpg --armor --output public.asc --export ``` ### 3. GitHub紐付け作業 {#3-dot-github紐付け作業} こちらを参考に対応する。 `openpgp4fpr:` を含めたGistを作成する。 Gistのリンクを `notation` を付与する。 ```console $ gpg --edit-key > notation @Enter the notation: proof@ariadne.id=https://gist.github.com/takeokunn/51d8c50bb4e6f08ff50d68a125d7fb54 > save ``` 次のコマンドで `public.asc` を生成して管理画面からアップロードする。 ```console $ gpg --armor --output public.asc --export ``` ### 4. DNS紐付け作業 {#4-dot-dns紐付け作業} こちらを参考に対応する。 `openpgp4fpr:` を含めたTXT Recordを作成する。 ```console $ dig TXT takeokunn.org | grep openpgp takeokunn.org. 300 IN TXT "openpgp4fpr:0B10DAA7BA0236D7382287660F79C0AB03FD7A1C" ``` DNSの情報を `notation` を付与する。 ```console $ gpg --edit-key > notation @Enter the notation: proof@ariadne.id=dns:takeokunn.org?type=TXT > save ``` 次のコマンドで `public.asc` を生成して管理画面からアップロードする。 ```console $ gpg --armor --output public.asc --export ``` ## 得られた結果・所感 {#得られた結果-所感} 多少作業が詰まったが、無事keyoxide上で証明できた。 {{< figure src="/images/480ABAFE-75DD-415C-99FB-941039A5D1FD.png" >}} `showpref` で現在の `notations` を確認できる。 ```console @gpg> showpref [ultimate] (1). takeo obara Cipher: AES256, AES192, AES, 3DES AEAD: OCB, EAX Digest: SHA512, SHA384, SHA256, SHA224, SHA1 Compression: ZLIB, BZIP2, ZIP, Uncompressed Features: MDC, AEAD, Keyserver no-modify Notations: proof@ariadne.id=https://gist.github.com/takeokunn/51d8c50bb4e6f08ff50d68a125d7fb54 proof@ariadne.id=https://twitter.com/takeokunn/status/1927387715441414384 proof@ariadne.id=dns:takeokunn.org?type=TXT ``` ## 今後の展開・検討事項 {#今後の展開-検討事項} 今回はkeyoxideに入門しただけなので、keyoxide自体の仕組みや実際の実装を読んで理解を深めたい。 -------------------------------------------------------------------------------- title: "Excelファイルをtxtファイルに変換してNotebookLMに食わせる" date: "2025-05-23" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 利用規約の関係で明確に言及できないが、某ECプラットフォーム(以下 A社)とのAPI連携を実装していた。 A社のAPI含む全体的なドキュメントの管理方法は次のようになっている。 - APIの情報がWeb上に公開されていない - 技術ブログに書いている人がいない - 管理画面を使うには申請が必要 - 管理画面の奥の方にAPIドキュメントがある - APIドキュメントをzipでダウンロードする方式 - EndpointごとにExcelファイルが入っている - セルの結合などを駆使して見易いように書かれている - Excelのシートが複数枚ある - エラーメッセージなどは別のダウンロードリンクに纏まっている 開発する上で次のような問題を抱えていた。 - 今回の対応で必要なEndpointの取捨選択を効率的にしたい - ひとつずつExcelファイルを開いて確認するのが面倒 - 横断した情報の整合性を確認するのが大変 今回のようにExcelファイルが与えられた時にどう対応するべきなのか後学の為にメモしておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. ExcelファイルをGoogle Driveにアップロードする {#1-dot-excelファイルをgoogle-driveにアップロードする} ExcelファイルをGoogle DriveにアップロードするとSpreadSheetから閲覧できる。 こうすることで、ExcelファイルにURL Linkからアクセスすることが可能になるのがよい。 ### 2. Excelファイルを文字列に起こす {#2-dot-excelファイルを文字列に起こす} `xlsx2csv` を使ってcsvファイルに変換する。 ```console $ xlsx2csv -s 2 tmp.xlsx > tmp.csv ``` `-s` オプションでシート番号を指定できる。 ```console usage: xlsx2csv [-h] [-v] [-a] [-c OUTPUTENCODING] [-d DELIMITER] [--hyperlinks] [-e] [--no-line-breaks] [-E EXCLUDE_SHEET_PATTERN [EXCLUDE_SHEET_PATTERN ...]] [-f DATEFORMAT] [-t TIMEFORMAT] [--floatformat FLOATFORMAT] [--sci-float] [-I INCLUDE_SHEET_PATTERN [INCLUDE_SHEET_PATTERN ...]] [--exclude_hidden_sheets] [--ignore-formats IGNORE_FORMATS [IGNORE_FORMATS ...]] [-l LINETERMINATOR] [-m] [-n SHEETNAME] [-i] [--skipemptycolumns] [-p SHEETDELIMITER] [-q QUOTING] [-s SHEETID] [--include-hidden-rows] xlsxfile [outfile] xlsx to csv converter positional arguments: xlsxfile xlsx file path, use '-' to read from STDIN outfile output csv file path options: -h, --help show this help message and exit -v, --version show program's version number and exit -a, --all export all sheets -c OUTPUTENCODING, --outputencoding OUTPUTENCODING encoding of output csv ** Python 3 only ** (default: utf-8) -d DELIMITER, --delimiter DELIMITER delimiter - columns delimiter in csv, 'tab' or 'x09' for a tab (default: comma ',') --hyperlinks, --hyperlinks include hyperlinks -e, --escape Escape \r\n\t characters --no-line-breaks, --no-line-breaks Replace \r\n\t with space -E EXCLUDE_SHEET_PATTERN [EXCLUDE_SHEET_PATTERN ...], --exclude_sheet_pattern EXCLUDE_SHEET_PATTERN [EXCLUDE_SHEET_PATTERN ...] exclude sheets named matching given pattern, only effects when -a option is enabled. -f DATEFORMAT, --dateformat DATEFORMAT override date/time format (ex. %Y/%m/%d) -t TIMEFORMAT, --timeformat TIMEFORMAT override time format (ex. %H/%M/%S) --floatformat FLOATFORMAT override float format (ex. %.15f) --sci-float force scientific notation to float -I INCLUDE_SHEET_PATTERN [INCLUDE_SHEET_PATTERN ...], --include_sheet_pattern INCLUDE_SHEET_PATTERN [INCLUDE_SHEET_PATTERN ...] only include sheets named matching given pattern, only effects when -a option is enabled. --exclude_hidden_sheets Exclude hidden sheets from the output, only effects when -a option is enabled. --ignore-formats IGNORE_FORMATS [IGNORE_FORMATS ...] Ignores format for specific data types. -l LINETERMINATOR, --lineterminator LINETERMINATOR line terminator - lines terminator in csv, '\n' '\r\n' or '\r' (default: \n) -m, --merge-cells merge cells -n SHEETNAME, --sheetname SHEETNAME sheet name to convert -i, --ignoreempty skip empty lines --skipemptycolumns skip trailing empty columns -p SHEETDELIMITER, --sheetdelimiter SHEETDELIMITER sheet delimiter used to separate sheets, pass '' if you do not need delimiter, or 'x07' or '\f' for form feed (default: '--------') -q QUOTING, --quoting QUOTING quoting - fields quoting in csv, 'none' 'minimal' 'nonnumeric' or 'all' (default: minimal) -s SHEETID, --sheet SHEETID sheet number to convert --include-hidden-rows include hidden rows ``` 次のように再帰的に実行するのでもよい。 ```console $ find . -name '*.xlsx' -exec bash -c 'for f; do out="${f%.xlsx}.csv"; xlsx2csv -s 2 "$f" > "$out"; done' bash {} + ``` ### 3. NotebookLMにアップロードする {#3-dot-notebooklmにアップロードする} NotebookLMはcsvアップロードに対応していないので、いったんtxtに変換してアップロードする。 ```console $ find . -type f -name '*.csv' -print0 | while IFS= read -r -d '' file; do mv "$file" "${file%.csv}.txt"; done ``` あとはよしなにNotebookLMに質問する。 ## 得られた結果・所感 {#得られた結果-所感} 変換されたcsvはセル結合のせいでだいぶ酷い形式だが、NotebookLMを介することによって圧倒的に読みやすくなった。 管理画面上のAPIに関する情報もついでに食わせることによって自分の疑問がかなり晴れたし、どこから参照したのかも教えてくれるので自分でも確認できるのがNotebookLMのよい所だなとあらためて感じた。 `xlsx2csv` はnixpkgsで提供してくれているので、雑に `nix-shell` で隔離したShell環境に入ればよい。 ```console $ nix-shell -p xlsx2csv ``` ## 今後の展開・検討事項 {#今後の展開-検討事項} Excelだと読み辛いのでopenapi.yamlをNotebookLMに生成させて、openapiからTypeScriptを生成する。 -------------------------------------------------------------------------------- title: "DeepWiki調査メモ" date: "2025-05-18" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} Devin社が開発したDeepWikiが世間を賑わせていた。 [DeepWiki: あらゆるGitHubリポジトリに対応する最高のAIドキュメントジェネレーター](https://huggingface.co/blog/lynn-mikami/deepwiki-ja) にもあるとおり、DeepWikiは次のような機能を提供してくれている。 DeepWikiが生成するドキュメントは圧倒的に質が高く、まさしく顧客が求めていたものを提供してくれているなという感想。 - レポジトリ内のドキュメントを自動生成 - 対話型AIアシスタント - フローチャート生成 DeepWikiはDevinに契約していればPrivate Repoにも使えるが、個人がDevinを契約するのは流石にハードルが高い。 DeepWikiが自分にとってどれくらい使いものになるのか、またOSS版の [AsyncFuncAI/deepwiki-open](https://github.com/AsyncFuncAI/deepwiki-open) を使ってみて、どのくらいの精度で出力されるのか調査をした。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. 普段よく使うOSSにDeepWikiを使う {#1-dot-普段よく使うossにdeepwikiを使う} 普段よく使っていて、内部的にどう動いているのかある程度把握してるレポジトリをコード規模ごとに選定した。 書いてあることに嘘はないか、網羅性はどうか、実用に耐えうるかを確認した。 - 大規模(100万行) - e.g. - 中規模(10万行) - e.g. - 小規模(1万行以内) - e.g. Chatで普段使ってる人しか知らなさそうなことを質問しまくって精度を確認した。 その結果、ドキュメントの質も網羅性も自分の用途では申し分がないことが確認できた。 ### 2. DeepWikiに自分のPublic Repoを食わせてみる {#2-dot-deepwikiに自分のpublic-repoを食わせてみる} それなりのコード量のある自分のPublic RepoをDeepWiki食わせてみた。 - - 解説記事を書く手間が省けたし、自分しか知らないことをChatで対話しても明瞭に回答してくれた。 ### 3. OSS版のAsyncFuncAI/deepwiki-openを試す {#3-dot-oss版のasyncfuncai-deepwiki-openを試す} [AsyncFuncAI/deepwiki-open](https://github.com/AsyncFuncAI/deepwiki-open/) をLocal立ち上げて動かしてみる。 {{< figure src="/images/912E7F43-2598-4390-B7CA-9D560EFF82F9.png" >}} 記事公開日現在、DeepWikiで生成されたものとdeepwiki-openが生成したものを見比べてみた。 DeepWikiには多少劣るが、かなりのクオリティで出力されるようになった。 - - {{< figure src="/images/6432A8D0-34A8-487A-AD44-D08D18ECEC93.png" >}} deepwiki-openにもチャット機能があるが、こちらも十分に機能してくれていることを確認した。 ## 得られた結果・所感 {#得られた結果-所感} DeepWikiのクオリティが圧倒的に高いことがわかったので、今後OSSコードリーディングにDeepWikiを利用していくことにした。 deepwiki-openは当初触った時、あまり網羅性がなく質の悪いアウトプットだったが、2週間くらい経って一気にクオリティが上がった。 AI関連ツールの進化は尋常じゃないくらい早いので2週間前に使ってイマイチだったからといって使えないと判断するのは早計だなと改めて感じた。 [@ryoppippi](https://x.com/ryoppippi) がDeepWikiのbadgeを作れるOSSを作っていたので流行に乗って [takeokunn/blog](https://github.com/takeokunn/blog) と [takeokunn/nixos-configuration](https://github.com/takeokunn/nixos-configuration) に配置した。(Thanks ryopi) ## 今後の展開・検討事項 {#今後の展開-検討事項} Aiderに食わせる為のコーディング特化のDeepWikiを作りたいのでMastraで自作する。 ## Backlinks {#backlinks} - [php-srcとyieldについて調査メモ]({{< relref "20250625115611-yield_in_php_src.md" >}}) -------------------------------------------------------------------------------- title: "Emacs lsp-modeに新しいClientを追加する方法" date: "2025-05-18" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} メジャーなEmacsのLSP Clientは3種類ある。 - [eglot](https://github.com/joaotavora/eglot) - [lsp-mode](https://github.com/emacs-lsp/lsp-mode) - [lsp-bridge](https://github.com/manateelazycat/lsp-bridge) それぞれメリットデメリットがあるが、自分としてはlsp-modeを推していて今後も使い続けるつもりでいる。 lsp-mode最大のメリットはClientサポート数の多さにあり、メジャーなLSP Clientなら必ず収録されていると言っても過言ではない。 過去に次の2つのClientを追加するPRを出したことがあるので、後学の為にも追加方法を纏めておく。 - [Add support for Python(ty) #4785](https://github.com/emacs-lsp/lsp-mode/pull/4785) - 追加で修正してくれた [lsp-python-py: make it add-on and activated on python #4786](https://github.com/emacs-lsp/lsp-mode/pull/4786) - [Add support for Jsonnet #4283](https://github.com/emacs-lsp/lsp-mode/pull/4283) ## 試したこと・やったこと {#試したこと-やったこと} ### 0. Language Server選定 {#0-dot-language-server選定} 追加したいLSP Serverをみつける。 流石に個人が趣味で開発した中途半端なものは避けるべきだと思うが、そこは取り込む人が判断するのでとりあえず出してみるのがよいかもしれない。 CONTRIBUTINGを熟読して、Pull Requestにすでにあるかどうかを確認する。 ### 1. Repoをforkして手元に落とす {#1-dot-repoをforkして手元に落とす} [emacs-lsp/lsp-mode](https://github.com/emacs-lsp/lsp-mode) からforkボタンを押して、自分の手元にGit Cloneする。 EmacsからPATHを通して `(require 'lsp-mode)` を実行する。 ### 2. lsp-modeの設定をする {#2-dot-lsp-modeの設定をする} `clients/lsp-python-ty.el` のように `clients/` に空気を読んでファイルを作成する。 Jsonnetの場合は以下。 最低限次の2つを定義すればよい。 完成度の高いLanguage Serverはオプションがあるので `defcustom` で都度定義する。 - `server-executable` - `lsp-register-client` ```emacs-lisp (require 'lsp-mode) (defgroup lsp-jsonnet nil "LSP support for jsonnet." :group 'lsp-mode :link '(url-link "https://github.com/grafana/jsonnet-language-server")) (defcustom lsp-clients-jsonnet-server-executable '("jsonnet-language-server") "The jsonnet language server executable to use." :group 'lsp-jsonnet :risky t :type '(repeat string)) (lsp-register-client (make-lsp-client :new-connection (lsp-stdio-connection (lambda () lsp-clients-jsonnet-server-executable)) :activation-fn (lsp-activate-on "jsonnet") :priority -1 :major-modes '(jsonnet-mode) :server-id 'jsonnet-lsp)) (lsp-consistency-check lsp-jsonnet) (provide 'lsp-jsonnet) ``` ### 3. 手元で動作確認する {#3-dot-手元で動作確認する} ちょうどよいサイズのプロジェクトを手元に用意して実際に動かしてみる。 基本的なLSP機能が正常に動くか確認する。 - 定義ジャンプ - 構文エラー表示 - Rename - 補完 ### 4. Pull Requestを出す {#4-dot-pull-requestを出す} lsp-mode.elのlsp-client-packagesにclient追加をする必要がある。 また[ドキュメント](https://emacs-lsp.github.io/lsp-mode/)やCHANGELOGに反映させる必要があるので、次のファイルを追加する。 - [docs/lsp-clients.json](https://github.com/emacs-lsp/lsp-mode/blob/8a266b83ea0fb880ef697771893c41f8745a04de/docs/lsp-clients.json) - [lsp-mode.el](https://github.com/emacs-lsp/lsp-mode/blob/8a266b83ea0fb880ef697771893c41f8745a04de/lsp-mode.el) - [mkdocs.yml](https://github.com/emacs-lsp/lsp-mode/blob/8a266b83ea0fb880ef697771893c41f8745a04de/mkdocs.yml) - [CHANGELOG.org](https://github.com/emacs-lsp/lsp-mode/blob/8a266b83ea0fb880ef697771893c41f8745a04de/CHANGELOG.org) あとはdescriptionを書いてPRを出し、レビュワーの指示にしたがって修正する。 ## 得られた結果・所感 {#得られた結果-所感} lsp-modeプロジェクトは非常に良くできているので追加が簡単。 レビュワーから速攻レビューが返ってきたので本当に助かった。 ## 今後の展開・検討事項 {#今後の展開-検討事項} 新しいLanguage Serverを見つけたら積極的にPull Requestを出していきたい。 -------------------------------------------------------------------------------- title: "GitHub Actions内で署名付きCommitをする" date: "2025-05-18" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} [Terraform GitHub Providerを導入した]({{< relref "20250402001222-setup_private_terraform_github_provider.md" >}}) で署名付きCommitを強制するBranch Ruleを網羅的に適用した。 ```terraform resource "github_repository_ruleset" "default-branch" { name = "main" repository = "repo-name" target = "branch" enforcement = "active" conditions { ref_name { include = ["~DEFAULT_BRANCH"] exclude = [] } } rules { required_signatures = true non_fast_forward = true } } ``` 2025年5月現在、 [takeokunn/nixos-configuration](https://github.com/takeokunn/nixos-configuration) では次のGitHub Actionsが動いている。 1. 毎日20時に発火する 2. `nix flake update` などを実行して依存を更新する 3. 変更をcommitしてpushする 署名付きCommitを強制する変更を加えた影響で「3. 変更をcommitしてpushする」が動かなくなってしまった。 調べた所、GitHub Actions内で署名付きCommitをしている人が世の中に少なかったので対応方法を纏めておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. 署名用のGPG Keyを生成する {#1-dot-署名用のgpg-keyを生成する} [GPG KeyのYubiKey運用をはじめた]({{< relref "20250514155803-yubikey_gpg_operation.md" >}}) にあるとおり、自分の手元では主鍵副鍵運用をしている。 主鍵から `addkey` Signのみを付与した副鍵を生成する。 生成する際に必ずパスフレーズも設定する。 後に使うのでフィンガープリントもメモしておく。 ```console $ gpg -K --with-fingerprint ``` ### 2. GitHub Actions Secretに副鍵のPrivate Keyを登録する {#2-dot-github-actions-secretに副鍵のprivate-keyを登録する} `GPG_PRIVATE_KEY` と `PASSPHRASE` を設定する。 自分の場合はTerraform GitHub Provider経由で設定をした。 ```terraform resource "github_actions_secret" "nixos-configuration-GPG_PRIVATE_KEY" { repository = github_repository.nixos-configuration.name secret_name = "GPG_PRIVATE_KEY" plaintext_value = var.GPG_PRIVATE_KEY } resource "github_actions_secret" "nixos-configuration-PASSPHRASE" { repository = github_repository.nixos-configuration.name secret_name = "PASSPHRASE" plaintext_value = var.PASSPHRASE } ``` ### 3. GitHub Actionsに組込む {#3-dot-github-actionsに組込む} 全体像は以下。 [crazy-max/ghaction-import-gpg](http://github.com/crazy-max/ghaction-import-gpg) を使い、次のように `GPG_PRIVATE_KEY` と `PASSPHRASE` とfingerprintを設定する。 ```yaml steps: - name: Import GPG key uses: crazy-max/ghaction-import-gpg@v6 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.PASSPHRASE }} fingerprint: <控えておいたfingerprint> git_user_signingkey: true git_commit_gpgsign: true git_config_global: true ``` commit部分はsigned optionをつけてpushをする。 ```console git commit -S -m "Update lockfile" git push origin HEAD ``` ## 得られた結果・所感 {#得られた結果-所感} 多少強引だが無事に実現できた。 `GPG_PRIVATE_KEY` が漏れる経路は以下が考えられる。 - Terraform CloudのSecretが抜かれた時 - GitHub Actions Secretが抜かれた時 - [crazy-max/ghaction-import-gpg](http://github.com/crazy-max/ghaction-import-gpg) に不正なコードが入った時 副鍵にはSignしか降ってないので失効対応をすれば問題ない認識だが注意して扱う必要がある。 調査する過程で、GitHubの署名付きCommitはGPGだけでなくSSHやX.509も対応していることが分かった。 ## 今後の展開・検討事項 {#今後の展開-検討事項} そもそも論としてPull Requestを自動で作成しCIが通ったらMergeするようにすべきなので別途対応する。 -------------------------------------------------------------------------------- title: "個人的Local環境のGit Branch運用について" date: "2025-05-18" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 複数人で触っているRepoをLocalでGit操作する時、次のような問題が発生していた。 - 直pushしてしまった - 意図しない作業ブランチで作業してしまった - コンフリクト解消ミスで本来必要なコードを消してしまう GitHubのBranch RuleやCIなどでミスを防ぐべきだが、そういうのが整っていない環境というのは多々ある。 自衛のために個人的に行っているGit Branch運用について纏めておく。 当記事の前提は以下。 - Git ClientはMagit - Default Branchは `main` - GitHubを利用している ## 試したこと・やったこと {#試したこと-やったこと} ### 0. 方針を決める {#0-dot-方針を決める} 基本方針は以下。 - 最新のRemote Branchを常に取得する - デフォルトがLocal Branchが一切ない状態にする - `main` Branchすら用意しない - 作業するタイミング ****のみ**** Local Branchを作成する - 作業が終わったら必ずGit Pushする(Remote管理) ### 1. 手元から一切のLocal Branchを消す {#1-dot-手元から一切のlocal-branchを消す} 次のようなLocal Branchがあったとする。 ```console $ git branch feature/xxx feature/yyy feature/zzz * main ``` `origin/main` に移動して、 `main` Branch含めすべてのLocal Branchを削除する。 ```console $ git checkout origin/main $ git branch -D main $ git branch -D feature/xxx $ git branch -D feature/yyy $ git branch -D feature/zzz ``` 次のような状態になるとよい。 ```console $ git branch * (HEAD detached at origin/main) ``` ### 2. 最新のRemote Branchを取得 {#2-dot-最新のremote-branchを取得} 定期的にRemote BranchをFetchをする。 ```console $ git fetch ``` `~/.config/git/config` に次のようにprune optionを付けておくことをオススメしている。 ```cfg [fetch] prune = true pruneTags = true ``` [andrmuel/projectile-git-autofetch](https://github.com/andrmuel/projectile-git-autofetch) というEmacs Packageを使えば、開いてるプロジェクトを定期的にFetchすることが可能。 ```emacs-lisp (autoload-if-found '(projectile-git-autofetch-setup) "projectile-git-autofetch" nil t) (add-hook 'emacs-startup-hook #'projectile-git-autofetch-setup) (with-eval-after-load 'projectile-git-autofetch ;; config (setopt projectile-git-autofetch-notify nil) (setopt projectile-git-autofetch-interval 60) (setopt projectile-git-autofetch-fetch-args '("--no-progress" "--prune" "--prune-tags"))) ``` ### 3. Localで作業をする場合 {#3-dot-localで作業をする場合} ****必ず**** `origin/main` からCheckoutする。 ```console $ git branch * (HEAD detached at origin/main) $ git fetch $ git checkout -b feature/xxx ``` ひととおり作業が終わったらGit PushしてPull Requestを出す。 Pull Requestを出してレビュー状態になったら `origin/main` にCheckoutして作業Branchを消す。 レビュー指摘を貰って修正する時はRemote BranchからLocal Branchを落としてきて作業をする。(以下ループ) ```console $ git checkout feature/xxx ``` ### 4. Pull Request Reviewの場合 {#4-dot-pull-request-reviewの場合} GitHub上のレビューで完結せず、手元で動作確認したい時はFetchした上でRemote BranchにCheckoutする。 ```console $ git fetch $ git checkout origin/feature/zzz ``` GitHub CLIを使えば簡単にCheckoutできる。 `--detach` オプションをつければRemote BranchにCheckoutできる。 ```console $ gh pr checkout --detach 2191 ``` ### 5. Magitの場合 {#5-dot-magitの場合} Magitのtransient menuを拡張して `gh pr checkout` コマンドを拡張した。 `c p ` でRemote Branch、 `c P ` でLocal BranchにCheckoutできるようになった。 ```emacs-lisp (defun my/magit-gh-pr-checkout (pr-number detach) (let* ((args (append '("pr" "checkout") (when detach '("--detach")) (list (number-to-string pr-number)))) (cmd (string-join (cons "gh" args) " "))) (message "Executing: %s" cmd) (apply #'call-process "gh" nil nil nil args) (magit-refresh))) (defun my/magit-gh-pr-checkout-detach () (interactive) (let ((pr (read-number "GitHub PR number (detach): "))) (my/magit-gh-pr-checkout pr t))) (defun my/magit-gh-pr-checkout-normal () (interactive) (let ((pr (read-number "GitHub PR number (branch): "))) (my/magit-gh-pr-checkout pr nil))) (with-eval-after-load 'magit-branch (transient-append-suffix 'magit-branch "c" '("p" "Checkout PR (detach)" my/magit-gh-pr-checkout-detach)) (transient-append-suffix 'magit-branch "c" '("P" "Checkout PR (branch)" my/magit-gh-pr-checkout-normal))) ``` ## 得られた結果・所感 {#得られた結果-所感} 運用をはじめて半年くらい経ったが圧倒的にミスが減った。 特に最新の `main` Branchを取り込む作業が安全に行えるようになったのが良かった。 当初は運用コストかかるかなと思っていたが、Local Branchのケアが不要になってむしろコストが下がったのでよい施策だった。 ## 今後の展開・検討事項 {#今後の展開-検討事項} 手元の環境はあくまで手元なので本質的な解決ではない。 本来はGithub(Remote)側で解決するべき問題なのでBranch Ruleなどで解決をしたい。 また、[Terraform GitHub Providerを導入した]({{< relref "20250402001222-setup_private_terraform_github_provider.md" >}}) のように、GitHubの設定を統一的にTerraform管理できるように理想的な運用を模索したい。 ## あとがき {#あとがき} vim-jpでこの記事を参考にしてブログを書いてくれた人がいたのでメモしておく。 - [gitのdetatched head運用でmainの誤commitにサヨナラ - Zenn](https://zenn.dev/kawarimidoll/articles/94fe6d900ed4d6) - [Claude Codeを活用したNeovimでのGit運用フロー - Zenn](https://zenn.dev/mikinovation/articles/20250519-claude-code-pull-request) ## Backlinks {#backlinks} - [GitHub Actions上でtblsを使ってスキーマ情報を取得してAIでSQLを生成する]({{< relref "20250628165348-github_actions_tbls_ai_sql_generation.md" >}}) -------------------------------------------------------------------------------- title: "2025年5月 Macの再インストール手順メモ" date: "2025-05-14" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} たまたまMacを再インストールする必要があった。 普段、手元のマシンの環境構築手順を洗練させてたお陰で少ないステップで構築できたので何をやったのか後学の為にメモしておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 0. Macの生前整理と初期化動線 {#0-dot-macの生前整理と初期化動線} データが消えてもいいようにバックアップしておく。 基本的にGoogle DriveかGitHubにすべてが置いていて今日PCが故障しても問題ない運用にしている。 - 消えてほしくないデータをGoogle Driveに置く - commit漏れてるrepoがないか確認して必要ならpushしておく 初期化動線は通常のmacの初期セットアップなので割愛。 ### 1. homebrewを入れる {#1-dot-homebrewを入れる} terminal.appを開いてhomebrewをインストールする。 ```console $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" ``` ### 2. nixos-configurationを実行する {#2-dot-nixos-configurationを実行する} YubiKeyを挿した状態でnix buildを実行する。 必要なツールとmac固有の設定はほぼすべて自動で入るようになっている。 ```console $ git clone https://github.com/takeokunn/nixos-configuration $ cd nixos-configuration $ nix run nix-darwin --extra-experimental-features 'flakes nix-command' -- switch --flake .#OPL2212-2 --show-trace ``` ### 3. GPG/SSH回りを設定する {#3-dot-gpg-ssh回りを設定する} [GPG KeyのYubiKey運用をはじめた]({{< relref "20250514155803-yubikey_gpg_operation.md" >}}) にある「3. 新規端末で副鍵(Sign, Auth)を入れる」以降を行う。 GitHubのssh認証を確認する。 ```bash $ ssh -T git@github.com Hi takeokunn! You've successfully authenticated, but GitHub does not provide shell access. ``` ### 4. 必要なレポジトリを落としてsecret.orgをorg-babel-tangleする {#4-dot-必要なレポジトリを落としてsecret-dot-orgをorg-babel-tangleする} `$ ghq get` で必要なレポジトリを網羅的に落とす。 [credential管理をorg-encryptからpassword-storeに移行した]({{< relref "20250414001634-migrate_credential_manager_to_password_store.md" >}}) のとおり、 `M-x org-babel-tangle` でpassword-storeを実行して必要な設定ファイルのほぼすべてを配置できるようにしている。 ### 5. Mac固有の設定をする {#5-dot-mac固有の設定をする} #### 5.1. Keyboard周り {#5-dot-1-dot-keyboard周り} [Mac | Option + Tab キーで、同じアプリ内のウィンドウを切り替え可能にする - Qiita](https://qiita.com/Yinaura/items/10fe5fe0cb0a795a0f58) を参考に `Option+Tab` で切り替えられるようにした。 CAPS_LOCKなどはnix-darwin側で設定している。 #### 5.2. IME周り {#5-dot-2-dot-ime周り} [Mac で AquaSKK を Ctrl+Space で切り替えなくても良くする方法 - Zenn](https://zenn.dev/yoshiyoshifujii/articles/78798db6472bf4) を参考にAquaSKKのみ表示されるようにする。 sonoma 15.5ではなぜかできなかったので次の手順で行った。(thanks [@Omochice](https://github.com/Omochice)) 1. aquaskkを追加する 2. 日本語>日本語ローマ字を有効化 3. 日本語ローマ字の入力モード>英字を有効化 4. ABCを消す 5. 日本語ローマ字を消す {{< figure src="/images/7DFF4EC0-8C18-44F1-93B4-73B770624715.png" >}} #### 5.3. アプリ周り {#5-dot-3-dot-アプリ周り} Spotlightをdisabledにして[Raycast](https://www.raycast.com/)を有効にするなどの細かい対応をした。 ### 6. その他 {#6-dot-その他} 細かい対応を都度行った。 - Webサービスログイン作業 - Slackセットアップ - `gh auth login` や `terraform login` など - `phpactor index:build` などのキャッシュ温め作業 - `offlineimap` 実行 - 量が多くて半日くらいかかった - etc.. ## 得られた結果・所感 {#得られた結果-所感} 8〜9割はNix経由でセットアップが完了したので本当に楽だった。 直前に [GPG KeyのYubiKey運用をはじめた]({{< relref "20250514155803-yubikey_gpg_operation.md" >}}) 対応をしたが、想像以上にスムーズに設定できたので大満足。 [デスクトップ環境をdisposableに保つ](https://masawada.hatenablog.jp/entry/2022/09/09/234159) はArch Linuxだが、Nixならより簡単かつ宣言的に記述できるのでNixは素晴しいですね。 ## 今後の展開・検討事項 {#今後の展開-検討事項} chshがうまくいかなかったので、以下を参考に設定しておく。 - - `Option+Tab` の設定が面倒なのでKarabiner-Elementsを入れてもよいのかもしれない。 MacはGUI経由じゃないとできない固有の設定が多い(nix-darwinがサポートしてない部分)ので大変だったが、NixOSならもっと工数を減らせるはず。 NixOSの設定も頑張りたい。 -------------------------------------------------------------------------------- title: "巨大なLaravelレポジトリでphpactorを使うTips" date: "2025-05-14" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 手元で [intelephense](https://intelephense.com/) が最近調子が悪く、定義ジャンプや補完が全然効かなくなってしまった。 phpactorの方がintelephenseよりもちゃんと機能してくれる肌感があったが、いかんせん大きいプロジェクトでは重く使いものにならない印象を持っていた。 しかし、phpactor周辺の設定を徹底したら快適に動くようになってくれたので対応項目をメモしておく。 巨大なLaravelレポジトリの情報は以下。 | プロジェクト | version | コード行数 | phpstan level | |--------|---------|-------|-----------------| | A | 7.4 | 140万行超 | 5 | | B | 8.2 | 40万行 | max/strict-rule | マシンスペックは以下。 ```console $ nix run nixpkgs#fastfetch ..' obara@OPL2212-001 ,xNMM. ----------------- .OMMMMo OS: macOS Sequoia 15.5 arm64 lMM" Host: MacBook Pro (14-inch, 2021) .;loddo:. .olloddol;. Kernel: Darwin 24.5.0 cKMMMMMMMMMMNWMMMMMMMMMM0: Uptime: 1 day, 6 hours, 50 mins .KMMMMMMMMMMMMMMMMMMMMMMMWd. Packages: 1375 (nix-system), 52 (nix-default), 6 (brew), 14 (brew-cask) XMMMMMMMMMMMMMMMMMMMMMMMX. Shell: fish 4.0.2 ;MMMMMMMMMMMMMMMMMMMMMMMM: Display (Color LCD): 3600x2338 @ 120 Hz (as 1800x1169) in 14" [Built-in] :MMMMMMMMMMMMMMMMMMMMMMMM: DE: Aqua .MMMMMMMMMMMMMMMMMMMMMMMMX. WM: Quartz Compositor 278.4.7 kMMMMMMMMMMMMMMMMMMMMMMMMWd. WM Theme: Multicolor (Dark) 'XMMMMMMMMMMMMMMMMMMMMMMMMMMk Font: .AppleSystemUIFont [System], Helvetica [User] 'XMMMMMMMMMMMMMMMMMMMMMMMMK. Cursor: Fill - Black, Outline - White (32px) kMMMMMMMMMMMMMMMMMMMMMMd Terminal: tmux 3.5a ;KMMMMMMMWXXWMMMMMMMk. CPU: Apple M1 Pro (8) @ 3.23 GHz "cooc*" "*coo'" GPU: Apple M1 Pro (14) @ 1.30 GHz [Integrated] Memory: 20.76 GiB / 32.00 GiB (65%) Swap: Disabled Disk (/): 153.68 GiB / 460.43 GiB (33%) - apfs [Read-only] Local IP (en0): 172.16.80.163/18 Battery (bq40z651): 100% [AC connected, Charging] Power Adapter: 39W Locale: en_US.UTF-8 ``` なお、phpactorについてはPHPerKaigi2025パンフレット記事の [Phpactorから学ぶLanguage Server Protocolの仕組み](https://www.takeokunn.org/pdf/phperkaigi-2025-pamphlet.pdf) に纏めた。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. PHP Runtime周り {#1-dot-php-runtime周り} [個人的devenv運用]({{< relref "20250126135045-personal_devenv_tips.md" >}}) のとおり、devenv(Nix)経由でphpをインストールしている。 - xdebugをextensionに入れない - `memory_limit=10G` のように十分にメモリを確保しておく - `memory_limit=-1` はPCが固まるのでオススメできない [PHP8から追加されたJITについて学ぼう!]({{< relref "../permanent/20221222111924-about_php_jit.md" >}}) にあるとおりJITを有効にすると高速化するので、8.2の場合は次のようにJITを有効にしている。 ```nix { pkgs, ... }: { cachix.enable = false; dotenv.disableHint = true; env.COMPOSER_MEMORY_LIMIT = "4G"; packages = with pkgs; [ phpactor php82Packages.psysh ]; languages.php = { enable = true; package = pkgs.php82.buildEnv { extensions = { all, enabled }: with all; enabled ++ [ ]; extraConfig = '' memory_limit=10G [opcache] opcache.enable=1 opcache.enable_cli=1 opcache.jit=tracing ; function opcache.jit_buffer_size = 128M ''; }; }; } ``` ### 2. phpactor config.json周り {#2-dot-phpactor-config-dot-json周り} 公式サイトでもPerformanceのページがあるが、はっきり言ってこれだけではまったく足りない。 `/.phpactor.json` の設定例は以下。 phpactorはintegrationを提供してくれているが、有効にすると重いのですべてoffにする。 ポイントはtimeout設定。 `textDocument/references` をするとすぐtimeoutして候補がまったく出ないので可能な限り長く取る。 ```json { "phpunit.enabled": false, "language_server_phpstan.enabled": false, "language_server_php_cs_fixer.enabled": false, "language_server_php_cs_fixer.env": { "XDEBUG_MODE": "off", "PHP_CS_FIXER_IGNORE_ENV": false }, "php_code_sniffer.enabled": false, "completion_worse.experimantal": true, "indexer.exclude_patterns": [ "**/tmp/**/*", "**/tests/**/*" ], "language_server_worse_reflection.workspace_index.update_interval": 5000, "language_server.diagnostics_on_update": false, "language_server_indexer.workspace_symbol_search_limit": 2500, "language_server_reference_reference_finder.reference_timeout": 90, "language_server_reference_finder.soft_timeout": 60 } ``` ### 3. lsp-mode周り {#3-dot-lsp-mode周り} [emacs-lsp/lsp-mode](https://github.com/emacs-lsp/lsp-mode) の場合になってしまうが、公式のパフォーマンスページを熟読して設定する。 また、emacsはjson packageが遅いので[blahgeek/emacs-lsp-booster](https://github.com/blahgeek/emacs-lsp-booster)を設定する。 phpactorのtimeoutを伸ばしたのであわせてlsp-modeのtimoutも伸ばす。 ```emacs-lisp (setopt lsp-response-timeout 60) ``` ### 4. Laravel周り {#4-dot-laravel周り} [barryvdh/laravel-ide-helper](https://github.com/barryvdh/laravel-ide-helper) でメタ情報を増やす。 また、PHPStanを頑張って導入してプロジェクト内に型情報を可能な限り増やし、phpactorの処理を軽減させる。 LSPを立ち上げる前に必ず次のコマンドを叩いてcacheを温めておく。(140万行の方は10分くらいかかる) ```console $ phpactor index:build -v ``` ## 得られた結果・所感 {#得られた結果-所感} 140万行のプロジェクトAでも問題なく挙動してくれるようになった。 40万行のプロジェクトBの方はそもそもPHPStanが厳しいので、LSPが正確かつ高速に挙動してくれるので最高。 かなりの設定をoffにした結果とはいえ、phpactorがphp製だから重いというのもチューニング次第だなというのが設定した感想。 ## 今後の展開・検討事項 {#今後の展開-検討事項} まだまだチューニングできる部分がないか探して、もっと高速に動かせないか検討する。 プロジェクト内の型情報があればある程有利なので、型情報を増やす活動を積極的にしていきたい。 -------------------------------------------------------------------------------- title: "GPG KeyのYubiKey運用をはじめた" date: "2025-05-11" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 2025年5月の自分の手元の環境はGPGに重度に依存している。 - [Bitwardenからpassword-storeに移行した]({{< relref "20240108211345-migrate_bitwarden_to_password_store.md" >}}) - [Authyからpassword-store-otpに移行した]({{< relref "20240110123142-migrate_authy_to_password_store_otp.md" >}}) - [credential管理をorg-encryptからpassword-storeに移行した]({{< relref "20250414001634-migrate_credential_manager_to_password_store.md" >}}) GPG Keyの運用についてかねてから悩んでいたが、YubiKey運用が一番よいという結論に至った。 ちょうどMacを初期化するタイミングがあったので完全に移行した。 次の記事を参考にした。 - [GPG 鍵と YubiKey の運用メモ - Zenn](https://zenn.dev/lambdalisue/articles/gpg-with-yubikey-2024) - [YubikeyでOpenPGP鍵をセキュアに使う - κeenのHappy Hacκing Blog](https://keens.github.io/blog/2021/03/23/yubikeywotsukau_openpghen/) - [YubiKey(OpenPGP card)には主鍵を入れよう - ふわわあのへや](https://fuwa.dev/posts/yubikey/) ## 試したこと・やったこと {#試したこと-やったこと} ### 0. 運用方針を決める {#0-dot-運用方針を決める} [GPG 鍵と YubiKey の運用メモ - Zenn](https://zenn.dev/lambdalisue/articles/gpg-with-yubikey-2024) と同様、次の方針で運用してくことにした。 - YubiKey には主鍵と副鍵 (Encrypt) を焼き、持ち歩く - YubiKey の主鍵を利用して副鍵 (Sign, Auth) をデバイス毎に作成・保存しておく - コミット署名はデバイス毎の副鍵 (Sign) を利用する - SSH Keyはデバイス毎の副鍵(Auth) `gpg --export-ssh-key` 経由で端末ごとに生成する - keygrepを `~/.gnupg/sshcontrol` に入れる - 暗号化・復号機能は YubiKey の副鍵 (Encrypt) を利用する 元記事に加えてSSH KeyもGPG経由で生成することにした。 [Terraform GitHub Providerを導入した]({{< relref "20250402001222-setup_private_terraform_github_provider.md" >}}) でIaCの土壌が用意されているので、端末が増えるごとに毎回追加する運用にしていく。 GPGの仕組みについてはChatGPTにひたすら質問して不安な部分を潰した。 ### 1. YubiKeyを購入する {#1-dot-yubikeyを購入する} Amazonで [Yubico セキュリティキー YubiKey 5C NFC USB-C/FIDO2/WebAuthn/U2F/2段階認証/高耐久性/耐衝撃性/防水](https://www.amazon.co.jp/dp/B08DHL1YDL?ref=ppx_yo2ov_dt_b_fed_asin_title) を購入した。 古いファームウェアには脆弱性があるようで、最新の5.7が届くことを祈りながら注文したら問題なく届いてくれた。(サポートには問合せ済み) {{< figure src="/images/DAA52C19-34D9-4119-A071-7FE95005EAC3.png" >}} 代理店が完売状態が続いていて辛い。 ### 2. 主鍵と副鍵をYubiKeyに焼く {#2-dot-主鍵と副鍵をyubikeyに焼く} ありすえさんの記事を参考に焼いた。 特に詰まる所なく対応できた。 - [https://zenn.dev/lambdalisue/articles/gpg-with-yubikey-2024#主鍵の編集](https://zenn.dev/lambdalisue/articles/gpg-with-yubikey-2024#%E4%B8%BB%E9%8D%B5%E3%81%AE%E7%B7%A8%E9%9B%86) - [https://zenn.dev/lambdalisue/articles/gpg-with-yubikey-2024#yubikey-への焼き付け](https://zenn.dev/lambdalisue/articles/gpg-with-yubikey-2024#yubikey-%E3%81%B8%E3%81%AE%E7%84%BC%E3%81%8D%E4%BB%98%E3%81%91) ### 3. 新規端末で副鍵(Sign, Auth)を入れる {#3-dot-新規端末で副鍵-sign-auth-を入れる} こちらもありすえさんの記事を元に `addkey` をして副鍵を生成した。 元記事に加えて `Auth` も付与して生成した。 - [https://zenn.dev/lambdalisue/articles/gpg-with-yubikey-2024#各デバイスでの利用法](https://zenn.dev/lambdalisue/articles/gpg-with-yubikey-2024#%E5%90%84%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%81%A7%E3%81%AE%E5%88%A9%E7%94%A8%E6%B3%95) Authを付与するには `--expert` のオプションが必要なので注意。 ```console # bad $ gpg --edit-key # good $ gpg --expert --edit-key ``` ### 4. ssh keyを生成する {#4-dot-ssh-keyを生成する} gpg-agentを有効にして `~/.gnupg/gpg-agent.conf` に次のような設定をした。 ```cfg enable-ssh-support grab default-cache-ttl 86400 default-cache-ttl-ssh 86400 max-cache-ttl 86400 max-cache-ttl-ssh 86400 ``` keygrepを取得して `~/.gnupg/sshcontrol` にkeygripを書き込む。 ```console $ gpg -K --with-keygrip ``` ### 5. githubにssh keyを登録する {#5-dot-githubにssh-keyを登録する} [Terraform GitHub Providerを導入した]({{< relref "20250402001222-setup_private_terraform_github_provider.md" >}}) のとおり、terraform経由でssh keyを登録する。 ```console $ gpg --export-ssh-key ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOev0KNrycAZEyKdVRBehEKH0l1c8FL9fON4PiguGVOl openpgp:xxxx ``` ここから確認ができる。 GPG keyのpublic keyは変化ないので初回登録しておけばよい。 ## 得られた結果・所感 {#得られた結果-所感} 想像以上にスムーズにYubiKeyに移行できた。 なんだかんだ以前生成した `~/.ssh/id_ed25519` を使っていたので、SSH Keyから解放されたのが個人的には一番嬉しいポイントかもしれない。 ## 今後の展開・検討事項 {#今後の展開-検討事項} 冗長性を高めるために自宅に厳重に保管する用のYubikeyも買って主鍵を焼く。 また、管理課の端末を増やして主鍵副鍵運用をもっと洗練させていく。 この記事を書いている間に [【令和最新版】sshcontrolは使わないで!最近のGPGによるSSH認証のベストプラクティス](https://www.atalie.net/ja/blog/11:gpg-agent-bestpractice/) という記事を見つけたので別途調査する。 ## Backlinks {#backlinks} - [GitHub Actions内で署名付きCommitをする]({{< relref "20250518201409-signed_commit_in_github_actions.md" >}}) - [keyoxideセットアップ]({{< relref "20250528112340-setup_keyoxide.md" >}}) - [2025年5月 Macの再インストール手順メモ]({{< relref "20250514222418-reinstall_mac_with_nix.md" >}}) -------------------------------------------------------------------------------- title: "GitHub ActionsでCachixを導入する" date: "2025-05-10" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} NixにハマってからGitHub Actions上でNixを実行する機会が増えた。 Nixというツールの性質上、どうしても実行環境の準備に時間がかかってしまうのでキャッシュ機能を導入したいと以前から思っていた。 Cachixが一番メジャーなサービスであり、個人利用程度なら無料で使えるので今回はじめて利用してみた。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. Cachixの設定をする {#1-dot-cachixの設定をする} アカウントを新規作成して、New Cacheで作成した。 「Settings > Cache Auth Tokens」でアクセストークンを作成して,GitHub Actions Secretに `CACHIX_AUTH_TOKEN` に登録した。 ### 2. GitHub Actionsで設定する {#2-dot-github-actionsで設定する} reusable actionで次のように定義した。 reusable actionからは `secrets.XX` を参照できないので引数で渡す必要がある。 ```yaml name: Run setup-nix description: Set up Nix environment for GitHub Actions inputs: cachix-auth-token: required: true runs: using: composite steps: - uses: cachix/install-nix-action@v31 - uses: cachix/cachix-action@v14 with: name: takeokunn-blog authToken: '${{ inputs.cachix-auth-token }}' ``` 次のように呼び出すとよしなにCache HitしたらCachixからCopyしてくれるようになった。 ```yaml steps: - name: Setup nix uses: ./.github/actions/setup-nix with: cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} ``` あとはよしなにnixを実行する。 ```yaml # nix buildの場合 - name: Build zenn run: nix build .#build-zenn # nix developの場合 - name: flake check run: nix develop --command bash -c "textlint 'org/**/*.org'" ``` ## 得られた結果・所感 {#得られた結果-所感} [takeokunn/blog](https://github.com/takeokunn/blog) の場合、平均して1分でNix実行環境を用意できるようになり大幅なCI速度改善を実現できた。 {{< figure src="/images/E79CB8EB-4877-4997-AD8C-C78E1045C12E.png" >}} Cachixの導入は簡単で苦労した所が特になかった。 今回の対応に合わせてflake.nixを充実させた結果、LocalとGitHub Actions上で同一の環境を再現性高く構築できるようになったのが良かった。 ## 今後の展開・検討事項 {#今後の展開-検討事項} LintのチェックなどのCI部分もflake.nix化の対応したい。 -------------------------------------------------------------------------------- title: "markdown-preview-modeとxwidgetsでリアルタイムプレビューする" date: "2025-05-10" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 最近AIが生成したMarkdownを読む機会が増えた。 Emacs Buffer上で読むのもよいのだが、ブラウザ上でCSSを当てた方が読みやすいので今回対応した。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. markdown-preview-modeを用意する {#1-dot-markdown-preview-modeを用意する} - [ancane/markdown-preview-mode](https://github.com/ancane/markdown-preview-mode) をインストールする - Melpa経由で入る - Markdownをコンパイルする為に [pandoc](https://pandoc.org/) を用意する ### 2. markdown-preview-modeの設定をする {#2-dot-markdown-preview-modeの設定をする} solarized-lightのテーマを選んだ。 ```emacs-lisp (setq markdown-preview-stylesheets (list "http://thomasf.github.io/solarized-css/solarized-light.min.css")) ``` 既存のテーマで画像を表示すると表示ずれ発生するのでスタイルを上書きした。 ```emacs-lisp (defun my/markdown-preview-css-filter (orig-return) (concat orig-return "")) (advice-add 'markdown-preview--css :filter-return #'my/markdown-preview-css-filter) ``` previewを起動する時にxwidget経由で開くようにした。 ```emacs-lisp (defun my/markdown-preview--start-with-xwidget (orig-fun &rest args) (let ((browse-url-browser-function #'xwidget-webkit-browse-url)) (apply orig-fun args))) (advice-add 'markdown-preview--start :around #'my/markdown-preview--start-with-xwidget) ``` ## 得られた結果・所感 {#得られた結果-所感} 他エディタに遜色ないくらい体験が良くなった。 WebSocket Serverも立ててくれるのでリアルタイムプレビューも可能になった。 {{< figure src="/images/294497E0-19F6-44FD-8664-826BF09039CF.png" >}} ## 今後の展開・検討事項 {#今後の展開-検討事項} org-modeの場合は [org-preview-html](https://github.com/jakebox/org-preview-html) で実現できるのでxwidget対応をする。 -------------------------------------------------------------------------------- title: "org-roam-ui-liteでblogの関係性を可視化する" date: "2025-05-10" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} [org-roamで記事を管理しGitHub Actionsで適切に公開する]({{< relref "../permanent/20231219122351-how_to_manage_blog_by_org_roam.md" >}}) に書いたとおり [takeokunn.org](https://www.takeokunn.org/) は [org-roam](https://www.orgroam.com/) で管理している。 org-roamの可視化ツールに [org-roam-ui](https://github.com/org-roam/org-roam-ui) があるが、次のような問題を抱えていた。 - Next.jsでリッチに作られすぎてる - NixでPackagingするのが大変 - GitHub ActionsでBuildするのが大変 - GitHub PagesでHostingするのが大変 GitHub Pagesに継続的にHostingする仕組みを作りたいと考えて早n年経っていたが、taniさんが [org-roam-ui-lite](https://github.com/tani/org-roam-ui-lite) という軽量なorg-roam-uiを作ってくれたので今回導入してみた。 ## 試したこと・やったこと {#試したこと-やったこと} 変更はここに纏まっている。 ### 1. flake.nixでBuildできるようにする {#1-dot-flake-dot-nixでbuildできるようにする} `scripts/org-roam-ui.el` を用意した。 ```emacs-lisp (require 'org-roam) (setq org-roam-db-location "./org-roam.db") (setq org-id-locations-file "./.org-id-locations") (setq org-roam-directory default-directory) ``` flake.nixでorg-roam-ui-liteをimportして `stdenv.mkDerivation` でpackageを作成した。 ```diff diff --git a/hugo/flake.nix b/hugo/flake.nix index c35c860..155c924 100644 --- a/hugo/flake.nix +++ b/hugo/flake.nix @@ -3,9 +3,10 @@ nixpkgs.url = "github:NixOS/nixpkgs"; nur-packages.url = "github:takeokunn/nur-packages"; flake-utils.url = "github:numtide/flake-utils"; + org-roam-ui-lite.url = "github:tani/org-roam-ui-lite"; }; - outputs = { self, nixpkgs, nur-packages, flake-utils }: + outputs = { self, nixpkgs, nur-packages, flake-utils, org-roam-ui-lite }: flake-utils.lib.eachDefaultSystem ( system: let @@ -85,6 +86,22 @@ cp -r ./public $out/ ''; }; + build-org-roam-ui-lite = pkgs.stdenv.mkDerivation { + name = "build-org-roam-ui-lite"; + src = ./.; + nativeBuildInputs = with pkgs; [ + org-roam-ui-lite.packages.${system}.export + (emacsPkg.pkgs.withPackages (epkgs: (with epkgs.melpaPackages; [ org-roam ]))) + ]; + buildPhase = '' + rm -fr org/private/ + emacs --batch --load scripts/org-roam-ui.el --funcall org-roam-db-sync + org-roam-ui-lite-export -d org-roam.db -o ./public + ''; + installPhase = '' + cp -r ./public $out/ + ''; + }; }; } ); ``` `nix build` でbuildできることを確認する。 ```bash $ nix build .#build-org-roam-ui-lite ``` ### 2. GitHub Actionsに組み込む {#2-dot-github-actionsに組み込む} GitHub Actionsで `nix build` するだけ。 ```yaml build-hugo-org-roam-ui-lite: runs-on: ubuntu-latest needs: ci defaults: run: working-directory: ./hugo steps: - name: Checkout uses: actions/checkout@v4 - name: Setup nix uses: ./.github/actions/setup-nix with: cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - name: Build build-org-roam-ui-lite run: nix build .#build-org-roam-ui-lite - name: Upload coverage report to artifacts uses: actions/upload-artifact@v4 with: name: build-hugo-org-roam-ui-lite path: ./hugo/result ``` URLは `/graph` だけどリクエストは `/api/graphql.json` なのでディレクトリを移動させた。 ```yaml - name: Download org-roam-ui-lite artifacts uses: actions/download-artifact@v4 with: name: build-hugo-org-roam-ui-lite path: ./public/graph merge-multiple: true - name: Move api directory run: mv ./public/graph/api ./public/api ``` ## 得られた結果・所感 {#得られた結果-所感} GitHub Pagesで確認できるようになった。 {{< figure src="/images/A6299E02-E405-40F8-B357-890C10B93105.png" >}} CI Workflowが充実してきたが実行速度は3分程度に抑えられている。 {{< figure src="/images/5906E4D3-CCAC-4BB6-B733-F0146814FE74.png" >}} ## 今後の展開・検討事項 {#今後の展開-検討事項} org-roam-ui-liteが普及してほしいので別途紹介記事をZennに書く。 -------------------------------------------------------------------------------- title: "private-terraformをHCP Terraformに移行した" date: "2025-05-07" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} 個人用のTerraformプロジェクトを用意して、GitHubとCloudflareとNextDNSの管理をしている。 - [Terraform GitHub Providerを導入した]({{< relref "20250402001222-setup_private_terraform_github_provider.md" >}}) - [お名前comからCloudflareにドメイン移管してTerraformで管理する]({{< relref "20250403220528-migrate_onamae_to_cloudflare_and_managed_by_terraform.md" >}}) - [NextDNSを導入した]({{< relref "20250402012730-setup_nextdns.md" >}}) tfstateのファイルをLocalマシンで雑に管理をしていたのだが、PCの引越し作業が発生したので管理方法を検討する必要があったので対応したことを纏めておく。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. tfstate管理方法を検討 {#1-dot-tfstate管理方法を検討} - MUST - 料金が無料 - セキュリティリスクが低い - 安全なterraform実行環境の保証 - SHOULD - バージョン管理 - 実行結果の可視性 - OPTIONAL - CI統合 HCP Terraform(旧terraform cloud)はHashiCorp謹製のCloudサービス。 500 resourcesまでは無料なので個人利用なら無料で完結する。 候補としてはさくらインターネットやCloudflareのストレージに上げることも検討していたが、そもそも各サービスを利用しなくなることも考えられるので却下した。 ### 2. HCP Terraform自体のTerraformを書く {#2-dot-hcp-terraform自体のterraformを書く} 以下を参考にterraformを書いた。 `TF_API_TOKEN` は管理画面から手ぽちで入力している。 ```terraform terraform { required_version = "1.11.4" cloud { organization = "takeokunn-private" workspaces { name = "tfe" } } required_providers { tfe = { source = "hashicorp/tfe" version = "0.65.2" } } } variable "TF_API_TOKEN" { type = string } provider "tfe" { token = var.TF_API_TOKEN } resource "tfe_organization" "private" { name = "takeokunn-private" email = "bararararatty@gmail.com" collaborator_auth_policy = "two_factor_mandatory" } resource "tfe_project" "default" { organization = tfe_organization.private.name name = "private" } resource "tfe_workspace" "tfe" { name = "tfe" organization = tfe_organization.private.name project_id = tfe_project.default.id } resource "tfe_workspace" "nextdns" { name = "nextdns" organization = tfe_organization.private.name project_id = tfe_project.default.id } resource "tfe_workspace" "cloudflare" { name = "cloudflare" organization = tfe_organization.private.name project_id = tfe_project.default.id } resource "tfe_workspace" "github" { name = "github" organization = tfe_organization.private.name project_id = tfe_project.default.id } ``` ### 3. ディレクトリ構成変更 {#3-dot-ディレクトリ構成変更} 元々はentrypoint1つですべてを流せるようにしていたが、次のようにサービスごとに分け、 `cd` で移動してからterraformを実行する運用に変更した。 この対応によって他サービスの変更に依存せずにversionを上げられるようになった。 ```console $ nix run nixpkgs#tree . . ├── projects │   ├── cloudflare │   │   └── main.tf │   ├── github │   │   ├── archive_repo.tf │   │   ├── main.tf │   │   ├── private_repo.tf │   │   ├── public_repo.tf │   │   └── user_settings.tf │   ├── nextdns │   │   └── main.tf │   └── tfe │   └── main.tf └── README.org ``` ### 4. 既存リソースの移行 {#4-dot-既存リソースの移行} `terraform import` を気合で入力して既存リソースの移行作業をした。 ディレクトリ構成変更が功を奏して小さく移行できた。 ## 得られた結果・所感 {#得られた結果-所感} terraformの安全な実行環境を手に入れたので非常に満足している。 特に難易度の高い所はなかったのですんなり導入できた。 ## 今後の展開・検討事項 {#今後の展開-検討事項} 現状管理画面に手ぽちで直接秘密鍵を入れているので、sops経由で登録できるようにしたい。 ## Backlinks {#backlinks} - [Typstで履歴書と職務経歴書をBuildする]({{< relref "20250619103829-build_cv_resume_with_typst.md" >}}) -------------------------------------------------------------------------------- title: "Typstテーマを自作した" date: "2025-05-06" -------------------------------------------------------------------------------- ## 背景・動機 {#背景-動機} [NixでTypstをBuildしGitHub Pagesでホスティングする]({{< relref "20250126114218-build_and_deploy_typst_using_nix.md" >}}) でTypstから生成したPDFスライドを配信する仕組みを作った。 今後の登壇は可能な限りTypstから生成するつもりなのでテーマを自作した。 次の要件を満たす必要があった。 - ライトテーマ/ダークテーマなどテーマを簡単に切り替えられるようにする - ソースコードのシンタックスハイライトサポート - 数式サポート 個人的にDracula Themeが好きなのでDracula Theme風に作る。 ## 試したこと・やったこと {#試したこと-やったこと} ### 1. theme自作 {#1-dot-theme自作} [polylux](https://typst.app/universe/package/polylux/) をベースに自作した。[basic-polylux](https://typst.app/universe/package/basic-polylux/) あたりを参考に改造した。 シンタックスハイライトは [codelst](https://typst.app/universe/package/codly/) に [Dracula.tmTheme](https://github.com/dracula/textmate/blob/master/Dracula.tmTheme) を読ませてハイライトしている。 ```typst #import "@preview/codelst:2.0.2": sourcecode #import "@preview/polylux:0.4.0": * #let blue = rgb("#282a36") #let white = rgb("#f8f8f2") #let pink = rgb("#ff79c6") #let purple = rgb("#bd93f9") #let orange = rgb("#ffb86c") #let green = rgb("#50fa7b") #let gray = rgb("#6272a4") #let slide-title-header = toolbox.next-heading(h => { show: toolbox.full-width-block.with(fill: blue, inset: 1em) set text(fill: purple, size: 1.2em) strong(h) }) #let the-footer(content) = { set text(size: 0.8em) show: pad.with(.5em) set align(bottom) context text(fill: white.lighten(40%), content) h(1fr) } #let outline = toolbox.all-sections((sections, _current) => { enum(tight: false, ..sections) }) #let progress-bar = toolbox.progress-ratio(ratio => { set grid.cell(inset: (y: .03em)) grid( columns: (ratio * 100%, 1fr), grid.cell(fill: pink)[], grid.cell(fill: purple)[], ) }) #let new-section(name) = slide({ set page(header: none, footer: none) show: pad.with(20%) set text(size: 1.5em) name toolbox.register-section(name) progress-bar }) #let focus(body) = context { set page(header: none, footer: none, fill: blue, margin: 2em) set text(fill: orange, size: 1.5em) set align(center) body } #let divider = line(length: 100%, stroke: .1em + pink) #let setup( footer: none, text-font: "Migu", math-font: "Fira Math", code-font: "Fira Code", text-size: 23pt, body, ) = { set raw(theme: "./Dracula.tmTheme") set page( paper: "presentation-16-9", fill: blue, margin: (rest: 4em), footer: the-footer(footer), header: slide-title-header, ) set text( font: text-font, size: text-size, fill: white, ) set strong(delta: 100) show math.equation: set text(font: math-font) show raw: set text(font: code-font) set align(horizon) show emph: it => text(fill: pink, it.body) show heading.where(level: 1): _ => none body } ``` ### 2. flake.nixでBuildする {#2-dot-flake-dot-nixでbuildする} 以下抜粋。 ```nix { pkgs.stdenv.mkDerivation { inherit name; src = ./.; nativeBuildInputs = with pkgs; [ typst migu fira-math fira-code (emacsPkg.pkgs.withPackages (epkgs: with epkgs; [ org ox-typst ])) ]; buildPhase = '' emacs --batch --load scripts/ox-typst.el --file org/${name}/article.org --funcall org-typst-slide-export-to-typst export TYPST_FONT_PATHS="${pkgs.migu}/share/fonts/truetype/migu:${pkgs.fira-math}/share/fonts/opentype:${pkgs.fira-code}/share/fonts/truetype/NerdFonts/FiraCode/" export TYPST_PACKAGE_PATH="${typstPackagesCache}/typst/packages" cp ./themes/dracula.typ org/${name}/dracula.typ cp ./themes/Dracula.tmTheme org/${name}/Dracula.tmTheme typst compile org/${name}/article.typ ''; installPhase = '' mkdir -p $out cp org/${name}/article.pdf $out/${name}.pdf ''; }; } ``` ### 3. org-modeでスライドを作成 {#3-dot-org-modeでスライドを作成} `#+begin_export typst` 内にtypstを記述する。 ```org #+TYPST: #import "./dracula.typ": * #+TYPST: #show: setup * Title スピーカーノートを記述。 #+begin_export typst #slide[ #set page(header: none, footer: none, margin: 3em) #text(size: 1.3em)[ ,*こんにちは世界* ] My subtitle #divider #set text(size: .8em, weight: "light") The Author Jan 16, 2025 Some extra info ] #+end_export ``` ## 得られた結果・所感 {#得られた結果-所感} PDFを生成できるようになった。 ![](/images/2EC21D4B-FC41-44C1-A5E0-8F2E6F326812.jpg) ![](/images/0C793D17-EBA9-49C1-9679-CC1C9ED4359B.jpg) ![](/images/5A54F358-F53F-4295-B219-9C7DC400DE7F.jpg) ![](/images/4CF07E20-20B6-4B4A-838D-C7071F3A2B03.jpg) ![](/images/4F026E33-FB7C-458D-82CD-4393F0F5297B.jpg) ![](/images/61370467-F63F-44F7-A8C8-0653BF33F660.jpg) ![](/images/64AED4DD-9B08-42A0-A4C6-83C0E46328E7.jpg) ## 今後の展開・検討事項 {#今後の展開-検討事項} 今後の発表でガンガン使うことによってtypstに慣れたい。 また、ライトテーマの自作やレイアウトテンプレートの充実など自作テーマの幅を広げたい。 ## Backlinks {#backlinks} - [Typst SlideのDracula Light Theme作成した]({{< relref "20251124235900-typst_slide_light_theme.md" >}}) - [Typst自作テーマで登壇スライドを作成した]({{< relref "20250601214129-create_slide_by_typst_self_theme.md" >}}) - [Typstとpdfpcでプレゼン機能を実現する]({{< relref "20250608145128-presenting_with_typst_pdfpc.md" >}}) -------------------------------------------------------------------------------- title: "nixpkgsのunstableでEmacs Nativecomp Buildができない問題とワークアラウンド" date: "2025-04-17" -------------------------------------------------------------------------------- ## Introduction {#introduction} 2025年4月16日現在、 `aarch64-darwin` 環境でnixpkgs unstableのemacs(nativecomp)をビルドできない問題が発生している。 ## ワークアラウンド方法 {#ワークアラウンド方法} packageに `withNativeCompilation = false;` を書く。 home-managerをoverlayする場合: ```nix { emacs-overlay }: [ (import emacs-overlay) # bug: https://github.com/NixOS/nixpkgs/issues/395169 (final: prev: { emacs = prev.emacs.override { withNativeCompilation = false; }; emacs-unstable = prev.emacs-unstable.override { withNativeCompilation = false; }; emacs-git = prev.emacs-git.override { withNativeCompilation = false; }; }) ] ``` pkgsをoverrideする場合: ```nix { pkgs }: pkgs.emacs.override { withNativeCompilation = false; } ``` ## 原因 {#原因} 「emacs: fails to launch on macOS Sequoia 15.4 #395169」に纏まってる。 macOS Sequoia 15.4でLC_RPATHが重複して登録されると `Duplicate LC_RPATH are deprecated` で落ちるようになった。 次のようなshell scriptを実行することで解決しているDraft PRがあるが、まだ時間がかかりそうな印象。 ```bash # maxOS Sequoia 15.4 updated their link-loader to refuse to evaluate # dylibs that include duplciate LC_RPATH instructions. Some libraries # haven't properly fixed this yet, and some internal NixOS builds seem # to cause this type of issue to occur. This hook simply cleans up any # duplicates detected inside dylib files. fixupOutputHooks+=('fixDarwinDuplicateRpathsIn $prefix') removeDarwinDuplicateRpaths() { dylib_path=$1 duplicates=$(@targetPrefix@otool -l "$dylib_path" | awk '/cmd LC_RPATH/{getline; getline; paths[$2]+=1} END { for (p in paths) if (paths[p]>1) print p }') if [ -n "$duplicates" ]; then echo "$dylib_path: removing duplicates" echo "$duplicates" while IFS= read -r dup; do @targetPrefix@install_name_tool $dylib_path -delete_rpath "$dup" done <<< "$duplicates" fi } fixDarwinDuplicateRpathsIn() { local dir="$1" dirs=$(find $dir -name "*.dylib") if [ -n "$dirs" ]; then while IFS= read -r dylib_path; do removeDarwinDuplicateRpaths $dylib_path done <<< "$dirs" fi } ``` ## 終わりに {#終わりに} nativecompが有効になってなくても速度面でストレスはないので、暫くはこの運用を続けようと思う。 -------------------------------------------------------------------------------- title: "credential管理をorg-encryptからpassword-storeに移行した" date: "2025-04-14" -------------------------------------------------------------------------------- ## Introduction {#introduction} credential管理をorg-encryptからpassword-storeに移行したので作業内容をメモしておく。 ## 現状の運用と問題 {#現状の運用と問題} [org-crypt運用メモ]({{< relref "20250127144907-my_usage_org_crypt.md" >}}) に書いたとおり、 `~/.aws/credentials` のような秘匿情報を `secret.org` を `org-babel-tangle` して出力していた。 次のように `org-decrypt-entries` と `org-encrypt-entries` をadviceして対応していたのだが、[org-nix-shell](https://github.com/AntonHakansson/org-nix-shell) がうまく動かなくなってしまった。 ```emacs-lisp (advice-add 'org-babel-tangle :before #'org-decrypt-entries) (advice-add 'org-babel-tangle :after #'org-encrypt-entries) ``` [ob-async](https://github.com/astahlman/ob-async) を有効にした時も[org-nix-shell](https://github.com/AntonHakansson/org-nix-shell)が実行できなくなってしまったので、 `advice-add` をすると問題が起きるのかもしれない。(要検証) [org-nix-shell入門]({{< relref "20250126140928-introduction_org_nix_shell.md" >}}) にも書いたが、[org-nix-shell](https://github.com/AntonHakansson/org-nix-shell)はキラーアプリケーションなので消す訳にはいかない。 `M-x org-encrypt-entries` を手動で実行する運用にしていたが、うっかり叩き忘れたままcommitを何度かしてしまった。 [newren/git-filter-repo](https://github.com/newren/git-filter-repo)などを使ってcommit hisotryから消したが面倒だった。 また、[mcp-servers-nixを導入した]({{< relref "20250409210655-introduction_mcp_servers_nix.md" >}})時点では実装されてなかったが、[mcp-servers-nix](https://github.com/natsukium/mcp-servers-nix) は `passwordCommand` をサポートしてくれた。 よい機会なのでpassword-store管理に移行にすることにした。 ## 作業手順 {#作業手順} ### 1. 既存のorg-encryptしてた鍵情報をpassword-store管理下に移行 {#1-dot-既存のorg-encryptしてた鍵情報をpassword-store管理下に移行} 次のように秘密鍵をpassコマンドで検索できるようにする。 ```console pass show gemini/private ``` ### 2. org-babel templateを作成 {#2-dot-org-babel-templateを作成} passコマンドをwrapするコマンドを用意する。 ```org #+name: pass #+begin_src emacs-lisp :var id="" :results value raw (string-trim (shell-command-to-string (format "pass show %s" id))) #+end_src ``` ### 3. 各秘密鍵に埋め込み {#3-dot-各秘密鍵に埋め込み} `:noweb yes` で関数を実行できるようにする。 `M-x org-babel-tangle` を実行すると、秘密鍵の入った状態で `~/.aider.conf.yml` が出力される。 ```org ** ~/.aider.conf.yml #+begin_src yaml :tangle (expand-file-name "~/.aider.conf.yml") :mkdirp yes :noweb yes dark-mode: true model: gemini api-key: - gemini=<> #+end_src ``` ## 終わりに {#終わりに} 外部コマンドを叩いてる関係で多少tangleに時間がかかるようになってしまったが許容できる範囲内。 長年秘密鍵の管理方法について悩んでたけど方針が決まってよかった。 ## Backlinks {#backlinks} - [GPG KeyのYubiKey運用をはじめた]({{< relref "20250514155803-yubikey_gpg_operation.md" >}}) - [2025年5月 Macの再インストール手順メモ]({{< relref "20250514222418-reinstall_mac_with_nix.md" >}}) -------------------------------------------------------------------------------- title: "emacsでuniversal-ctagsを使う" date: "2025-04-09" -------------------------------------------------------------------------------- ## Introduction {#introduction} [universal-ctags/ctags](https://github.com/universal-ctags/ctags) の検証をしたのでメモしておく。 ## 概要 {#概要} Wikipedia的には以下。 > Ctags(英: Ctags)はソース及びヘッダ内にある名前のインデックス(又はタグ)ファイルを生成するプログラム。様々なプログラミング言語に対応している。言語に依存するが、サブルーチン(関数)、変数、クラスのメンバ、マクロ等がインデックス化される。これらのタグによりテキストエディタなどのツールで高速かつ容易に定義を参照できる。相互参照ファイルを出力でき、また名前についての情報を人が読みやすい形で列挙した言語ファイルを生成することもできる。 > > Ctagsはケン・アーノルドがBSD Unix用に開発した。後にJim KlecknerによりFortranがサポートされ、ビル・ジョイによりPascalがサポートされた。 universal-ctagsは「A maintained ctags implementation」と自称しているとおり、2025年4月現在も活発にメンテナンスされている。 ## universal-ctags使い方 {#universal-ctags使い方} Nix経由なら簡単に実行できる。 ```console $ nix run nixpkgs#universal-ctags -- --version Universal Ctags 6.1.0, Copyright (C) 2015-2023 Universal Ctags Team Universal Ctags is derived from Exuberant Ctags. Exuberant Ctags 5.8, Copyright (C) 1996-2009 Darren Hiebert URL: https://ctags.io/ Output version: 0.0 Optional compiled features: +wildcards, +regex, +gnulib_fnmatch, +gnulib_regex, +iconv, +option-directory, +xpath, +json, +interactive, +yaml, +case-insensitive-filenames, +packcc, +optscript, +pcre2 ``` PHPプロジェクトである [bobthecow/psysh](https://github.com/bobthecow/psysh) を例に `TAGS` を生成する。 ```console $ nix run nixpkgs#universal-ctags -- -R --languages=+php,-python -e . ``` `-e` オプションはEmacs用。 ```console -e Output tag file for use with Emacs. ``` たとえば `src/CodeCleaner.php` は次のように出力される。 ```console $ nix run nixpkgs#universal-ctags -- -R --languages=+php,-python -e src/CodeCleaner.php $ cat -p TAGS src/CodeCleaner.php,3540 namespace Psy;^?Psy^A12,215 use PhpParser\NodeTraverser;^?NodeTraverser^A14,231 use PhpParser\Parser;^?Parser^A15,260 use PhpParser\PrettyPrinter\Standard as Printer;^?Printer^A16,282 use Psy\CodeCleaner\AbstractClassPass;^?AbstractClassPass^A17,331 use Psy\CodeCleaner\AssignThisVariablePass;^?AssignThisVariablePass^A18,370 use Psy\CodeCleaner\CalledClassPass;^?CalledClassPass^A19,414 use Psy\CodeCleaner\CallTimePassByReferencePass;^?CallTimePassByReferencePass^A20,451 use Psy\CodeCleaner\CodeCleanerPass;^?CodeCleanerPass^A21,500 use Psy\CodeCleaner\EmptyArrayDimFetchPass;^?EmptyArrayDimFetchPass^A22,537 use Psy\CodeCleaner\ExitPass;^?ExitPass^A23,581 use Psy\CodeCleaner\FinalClassPass;^?FinalClassPass^A24,611 use Psy\CodeCleaner\FunctionContextPass;^?FunctionContextPass^A25,647 use Psy\CodeCleaner\FunctionReturnInWriteContextPass;^?FunctionReturnInWriteContextPass^A26,688 use Psy\CodeCleaner\ImplicitReturnPass;^?ImplicitReturnPass^A27,742 use Psy\CodeCleaner\IssetPass;^?IssetPass^A28,782 use Psy\CodeCleaner\LabelContextPass;^?LabelContextPass^A29,813 use Psy\CodeCleaner\LeavePsyshAlonePass;^?LeavePsyshAlonePass^A30,851 use Psy\CodeCleaner\ListPass;^?ListPass^A31,892 use Psy\CodeCleaner\LoopContextPass;^?LoopContextPass^A32,922 use Psy\CodeCleaner\MagicConstantsPass;^?MagicConstantsPass^A33,959 use Psy\CodeCleaner\NamespacePass;^?NamespacePass^A34,999 use Psy\CodeCleaner\PassableByReferencePass;^?PassableByReferencePass^A35,1034 use Psy\CodeCleaner\RequirePass;^?RequirePass^A36,1079 use Psy\CodeCleaner\ReturnTypePass;^?ReturnTypePass^A37,1112 use Psy\CodeCleaner\StrictTypesPass;^?StrictTypesPass^A38,1148 use Psy\CodeCleaner\UseStatementPass;^?UseStatementPass^A39,1185 use Psy\CodeCleaner\ValidClassNamePass;^?ValidClassNamePass^A40,1223 use Psy\CodeCleaner\ValidConstructorPass;^?ValidConstructorPass^A41,1263 use Psy\CodeCleaner\ValidFunctionNamePass;^?ValidFunctionNamePass^A42,1305 use Psy\Exception\ParseErrorException;^?ParseErrorException^A43,1348 class CodeCleaner^?CodeCleaner^A49,1550 private bool $yolo = false;^?yolo^A51,1570 private bool $strictTypes = false;^?strictTypes^A52,1602 private Parser $parser;^?parser^A54,1642 private Printer $printer;^?printer^A55,1670 private NodeTraverser $traverser;^?traverser^A56,1700 private ?array $namespace = null;^?namespace^A57,1738 public function __construct(?Parser $parser = null, ?Printer $printer = null, ?NodeTraverser^?__construct^A68,2359 public function yolo(): bool^?yolo^A85,2982 private function getDefaultPasses(): array^?getDefaultPasses^A95,3151 private function addImplicitDebugContext(array $passes)^?addImplicitDebugContext^A159,5472 private static function getDebugFile()^?getDebugFile^A195,6446 private static function isDebugCall(array $stackFrame): bool^?isDebugCall^A219,7096 public function clean(array $codeLines, bool $requireSemicolons = false)^?clean^A238,7790 public function setNamespace(?array $namespace = null)^?setNamespace^A263,8524 public function getNamespace()^?getNamespace^A273,8724 protected function parse(string $code, bool $requireSemicolons = false)^?parse^A288,9128 private function parseErrorIsEOF(\PhpParser\Error $e): bool^?parseErrorIsEOF^A322,10102 private function parseErrorIsUnclosedString(\PhpParser\Error $e, string $code): bool^?parseErrorIsUnclosedString^A336,10569 private function parseErrorIsUnterminatedComment(\PhpParser\Error $e, string $code): bool^?parseErrorIsUnterminatedComment^A351,10952 private function parseErrorIsTrailingComma(\PhpParser\Error $e, string $code): bool^?parseErrorIsTrailingComma^A356,11122 ``` 以下が読み方らしい。 | 行 | 説明 | |------------------------------------------------------------------------------------------|-----------------------| | namespace Psy;^?Psy^A12,215 | Psy という名前空間を定義している(行12) | | use PhpParser\Parser;^?Parser^A15,260 | Parser を use している(行15) | | class CodeCleaner^?CodeCleaner^A49,1550 | CodeCleaner クラスの定義(行49) | | private bool $yolo = false;^?yolo^A51,1570 | yolo プロパティの定義(行51) | | public function clean(array $codeLines, bool $requireSemicolons = false)^?clean^A238,7790 | clean 関数の定義(行238) | ## emacsとの繋ぎ込み {#emacsとの繋ぎ込み} 特に設定していなくても `/TAGS` があれば、任意の関数で `M-x xref-find-definitions(M-.)` を実行してジャンプできる。 xrefのbackendがetagsになり、次のようにTAGSのPATHを解決してる。 ```emacs-lisp (expand-file-name "TAGS" (locate-dominating-file default-directory "TAGS")) ``` - - buffer saveにhookしてctagsを再生成するのが一般的のようだ。 ## 終わりに {#終わりに} 思った以上に簡単に使えた。 php-srcやvimなどのOSSコードリーディングはLSP重いしこれでよいのかもしれない。 -------------------------------------------------------------------------------- title: "mcp-servers-nixを導入した" date: "2025-04-09" -------------------------------------------------------------------------------- ## Introduction {#introduction} natsukium謹製の [natsukium/mcp-servers-nix](https://github.com/natsukium/mcp-servers-nix) を [takeokunn/nixos-configuration](https://github.com/takeokunn/nixos-configuration) に導入したのでメモしておく。 ## 変更点 {#変更点} ### Before {#before} - Orgファイルから `org-babel-tangle` で `~/Library/Application\ Support/Claude/claude_desktop_config.json` を出力 - 必要なpackageは [node2nix](https://github.com/svanderburg/node2nix) 経由で入れる - 秘密鍵はOrgファイルで `org-encrypt` して管理 - ref. [org-crypt運用メモ]({{< relref "20250127144907-my_usage_org_crypt.md" >}}) ### After {#after} - [natsukium/mcp-servers-nix](https://github.com/natsukium/mcp-servers-nix) からMCPに必要なものをjsonを出力 - 必要なpackageは大体 [natsukium/mcp-servers-nix](https://github.com/natsukium/mcp-servers-nix) に同梱されてる - 足りないものは [node2nix](https://github.com/svanderburg/node2nix) 経由で入れる - 秘密鍵は [sops-nix](https://github.com/Mic92/sops-nix) で管理 ## 作業内容 {#作業内容} [MCPサーバーの設定をセキュアでGit friendlyにする - Zenn](https://zenn.dev/natsukium/articles/f010c1ec1c51b2) を事前に読む。 ### 1. sops-nixに登録 {#1-dot-sops-nixに登録} `sops/password.yml` に以下を登録する。 ```yaml brave-api-token: BRAVE_API_KEY=xxxx ``` `sops/default.nix` で読み込めるようにする。 ```nix { sops = { defaultSopsFile = ./password.yaml; secrets = { brave-api-token = { }; }; }; } ``` ### 2. mcp-serversを記述する {#2-dot-mcp-serversを記述する} `envFile = config.sops.secrets.brave-api-token.path;` のように環境変数を渡す。 ```nix { pkgs, config, mcp-servers-nix, nodePkgs, }: let programs = { fetch.enable = true; playwright.enable = true; brave-search = { enable = true; envFile = config.sops.secrets.brave-api-token.path; }; }; in [ { home.file."Library/Application\ Support/Claude/claude_desktop_config.json" = { source = mcp-servers-nix.lib.mkConfig pkgs { inherit programs; }; }; } ] ``` ### 3. home-managerに組込み {#3-dot-home-managerに組込み} `home-manager/advanced.nix` で `imports` で登録すればよい。 ```nix { config, system, nixpkgs, org-babel, emacs-overlay, mcp-servers-nix, ... }: let # --- 中略 --- pkgs = import nixpkgs { inherit system; config.allowUnfree = true; overlays = basicOverlay ++ advancedOverlay; }; nodePkgs = pkgs.callPackage ../node2nix { inherit pkgs; }; # mcp servers mcpServers = import ./mcp-servers { inherit pkgs nodePkgs; inherit config mcp-servers-nix; }; in { imports = mcpServers; } ``` ## 作業結果 {#作業結果} 正常に動くことを確認できた。 {{< figure src="/images/B895E989-CD33-4A5E-967D-66E6DD2F3401.png" >}} {{< figure src="/images/B7AFBD4E-D4F3-49E8-A809-DEA091F5B3B0.png" >}} `$ cat ~/Library/Application\ Support/Claude/claude_desktop_config.json`: ```json { "mcpServers": { "brave-search": { "args": [], "command": "/nix/store/ycnnbcc78f826p1qv1ishw51i7kizvpn-mcp-server-brave-search/bin/mcp-server-brave-search", "env": {} }, "fetch": { "args": [], "command": "/nix/store/bygbip4rxvmpyrcccznv69ssvg65985m-mcp-server-fetch-2025.3.28/bin/mcp-server-fetch", "env": {} }, "playwright": { "args": [ "--executable-path", "/nix/store/97prd65kac7lms777bigyjq56igmx2jq-google-chrome-135.0.7049.42/bin/google-chrome-stable" ], "command": "/nix/store/xp3hby1vl5ppf1xpmlnzj9b8lpnf94q1-playwright-mcp-0.0.9/bin/mcp-server-playwright", "env": {} } } } ``` ## 終わりに {#終わりに} sopsに雑にパスワードを登録してるのでもう少しオシャレに管理したい。 ```yaml brave-api-token: BRAVE_API_KEY=xxxx ``` ## Backlinks {#backlinks} - [node2nixの使い方]({{< relref "20250622133346-how_to_use_node2nix.md" >}}) - [credential管理をorg-encryptからpassword-storeに移行した]({{< relref "20250414001634-migrate_credential_manager_to_password_store.md" >}}) -------------------------------------------------------------------------------- title: "BitwardenとAuthyのアカウントを削除した" date: "2025-04-08" -------------------------------------------------------------------------------- ## Introduction {#introduction} [Bitwardenからpassword-storeに移行した]({{< relref "20240108211345-migrate_bitwarden_to_password_store.md" >}}) でBitwardenから離れて1年以上経った。 当初はBitwardenにしかない情報もあったのでを開くこともあったが、今になっては使うことがなくなったのでアカウントを完全に削除する。 また、[Authyからpassword-store-otpに移行した]({{< relref "20240110123142-migrate_authy_to_password_store_otp.md" >}})でAuthyも使わなくなったのでついでに削除する。 ## 作業手順 {#作業手順} ### 1. Bitwarden {#1-dot-bitwarden} 公式ドキュメントを参考に削除する。 [https://bitwarden.com/ja-jp/help/delete-your-account/](https://bitwarden.com/ja-jp/help/delete-your-account/) 「個人のアカウントを削除します > アカウントを削除」で特に警告も出ずにサクっと消えた。 {{< figure src="/images/F156CC55-0D2F-4F33-9B11-DD831588D5A7.png" >}} ### 2. Authy {#2-dot-authy} 公式の削除動線を辿って確認メールを踏んで削除する。 {{< figure src="/images/9AE8AD89-1FE5-4218-8EF8-0F31B915EF11.png" >}} ## 終わりに {#終わりに} BitwardenもAuthyも駆け出しエンジニアの頃から使ってたので思い入れのあるツールだった。 10年間ありがとう。 -------------------------------------------------------------------------------- title: "Denoで簡易的なCLIツールを作る" date: "2025-04-03" -------------------------------------------------------------------------------- ## Introduction {#introduction} DenoでCLIツールを試験的に作ったのでメモしておく。 ## 方針 {#方針} - [cliffy](https://cliffy.io/) で作成 - `cat` と `grep` をSub Commandで簡易的に作成 ## ディレクトリ構造 {#ディレクトリ構造} ```console $ nix run nixpkgs#tree . . ├── deno.json ├── deno.lock └── src ├── commands │   ├── cat.ts │   └── grep.ts ├── deps.ts └── main.ts 3 directories, 6 files ``` ## 作業手順 {#作業手順} ### 1. 依存関係をインストール {#1-dot-依存関係をインストール} `$ deno install` で入れて使い易いように `src/deps.ts` を用意する `deno.json`: ```json { "imports": { "@cliffy/command": "jsr:@cliffy/command@^1.0.0-rc.7" } } ``` `src/deps.ts`: ```typescript export { Command } from '@cliffy/command'; ``` ### 2. Command作成 {#2-dot-command作成} ```typescript import { Command } from './deps.ts'; import { catCommand } from './commands/cat.ts'; import { grepCommand } from './commands/grep.ts'; await new Command() .name('mycli') .version('0.1.0') .description('My CLI tool') .command('cat', catCommand) .command('grep', grepCommand) .parse(Deno.args); ``` ### 3. Sub Command {#3-dot-sub-command} #### 3.1 cat {#3-dot-1-cat} ```typescript import { Command } from '../deps.ts'; const displayFiles = async (files: string[]): Promise => { for (const file of files) { try { const content = await Deno.readTextFile(file); console.log(`--- ${file} ---`); console.log(content); } catch (err) { if (err instanceof Error) { console.error(`Error reading ${file}: ${err.message}`); } else { console.error(`Unknown error:`, err); } } } }; export const catCommand = new Command() .name('cat') .description('Display content of files') .arguments('') .action(async (_, ...files: string[]) => await displayFiles(files)); ``` #### 3.2 grep {#3-dot-2-grep} ```typescript import { Command } from '../deps.ts'; const grepFiles = async (pattern: string, files: string[]): Promise => { const regex = new RegExp(pattern, 'g'); for (const file of files) { try { const content = await Deno.readTextFile(file); const lines = content.split('\n'); let matchFound = false; for (let i = 0; i < lines.length; i++) { if (regex.test(lines[i])) { if (!matchFound) { console.log(`\n--- ${file} ---`); matchFound = true; } console.log(`${i + 1}: ${lines[i]}`); regex.lastIndex = 0; // Reset regex for next test } } } catch (err) { if (err instanceof Error) { console.error(`Error reading ${file}: ${err.message}`); } else { console.error(`Unknown error:`, err); } } } }; export const grepCommand = new Command() .name('grep') .description('Search for pattern in files') .arguments(' ') .action(async (_, pattern: string, ...files: string[]) => await grepFiles(pattern, files) ); ``` ### 4. Command実行 {#4-dot-command実行} #### 4.1 cat {#4-dot-1-cat} ```console $ deno run --allow-read src/main.ts cat deno.json src/deps.ts --- deno.json --- { "imports": { "@cliffy/command": "jsr:@cliffy/command@^1.0.0-rc.7" } } --- src/deps.ts --- export { Command } from '@cliffy/command'; ``` #### 4.2 grep {#4-dot-2-grep} ```console $ deno run --allow-read src/main.ts grep "command" deno.json src/main.ts --- deno.json --- 3: "@cliffy/command": "jsr:@cliffy/command@^1.0.0-rc.7" --- src/main.ts --- 2: import { catCommand } from './commands/cat.ts'; 3: import { grepCommand } from './commands/grep.ts'; 9: .command('cat', catCommand) 10: .command('grep', grepCommand) ``` ## 終わりに {#終わりに} TypeScriptで記述できるのはnpmの資産が使えて便利だし、cliffyも使い勝手が非常によい。 -------------------------------------------------------------------------------- title: "flake.nixでポータブルな最小構成のEmacsを作成する" date: "2025-04-03" -------------------------------------------------------------------------------- ## Introduction {#introduction} 「任意のEmacs Packageが動かない」がおま環かバグかというのを切り分けることは非常に重要。 誰でも同じ環境を整えられるようにすると問題解決が捗るので構築方法をメモしておく。 ## 作業手順 {#作業手順} ### 1. init.el作成 {#1-dot-init-dot-el作成} 今回のサンプルは[rainbow-delimiters](https://melpa.org/#/rainbow-delimiters)が動作するか確認するものにする。 ```emacs-lisp (use-package rainbow-delimiters :ensure t :hook (prog-mode-hook . rainbow-delimiters-mode)) ``` ### 2. flake.nix作成 {#2-dot-flake-dot-nix作成} `alwaysEnsure = true;` にすると `init.el` 内の `:ensure t` を読んでNix Build時によしなにインストールしてくれる。 また、[emacs-overlay](https://github.com/nix-community/emacs-overlay) を使うと `unstable/stable/head` など複数バージョンで実行できるので検証の幅を簡単に広げられる。 ```nix { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; emacs-overlay = { url = "github:nix-community/emacs-overlay"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { self, nixpkgs, emacs-overlay }: let system = "aarch64-darwin"; pkgs = import nixpkgs { inherit system; overlays = [ emacs-overlay.overlay ]; }; emacs = pkgs.emacsWithPackagesFromUsePackage { config = ./init.el; package = pkgs.emacs-unstable; alwaysEnsure = true; }; in { packages.${system}.default = emacs; }; } ``` ### 3. Nix Buildして実行 {#3-dot-nix-buildして実行} Localに一切依存しない形でEmacsをBuildできる。 ```console $ nix build .#default $ ./result/bin/emacs -nw -Q ``` ## 終わりに {#終わりに} 「最小構成を作ってください」の正解は `flake.nix` だと思っている。 余談だが、natsukiumの「一般構築魔法(Nix)のVimへの応用について」の「Nixでプラグインの実行環境を提供する」でも同じことが書かれている。 -------------------------------------------------------------------------------- title: "お名前comからCloudflareにドメイン移管してTerraformで管理する" date: "2025-04-03" -------------------------------------------------------------------------------- ## Introduction {#introduction} お名前comで管理していた `takeokunn.org` をCloudflareに移管したのでメモしておく。 ## Background {#background} お名前comには世の中のエンジニアと同じように、長年次の不満を抱えていた。 - 管理画面がとにかく使いづらい - 広告・営業メールが多い - 自動更新やオプションの設定が本当に分かりづらい - IaCに対応していない vim-jpで令和時代にドメインを管理するならCloudflare一択という意見をもらったので移管した。 ## 作業手順 {#作業手順} ドメインの移管はじめてだったこと、個人ドメインであることからダウンタイムが発生許容した。 2と3を入れ替えればダウンタイムが発生しなかったかもしれない。 ### 1. Cloudflareにアカウントを作成する {#1-dot-cloudflareにアカウントを作成する} 公式サイトから新規登録をしてクレジットカードを登録する。 [https://www.cloudflare.com/ja-jp/](https://www.cloudflare.com/ja-jp/) ### 2. お名前comからCloudflareに移管する {#2-dot-お名前comからcloudflareに移管する} [お名前.com から Cloudflare Registrar にドメイン移管した話 - Zenn](https://zenn.dev/muchoco/articles/9039762136e15c) を参考に移管した。 「トランスファー申請の承認」が15分以上かかったがいずれ来るので気長に待てばよい。 ### 3. Terraformで管理する {#3-dot-terraformで管理する} Terraform Cloudflare Providerを使う。 [https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs) #### 3.1 provider作成 {#3-dot-1-provider作成} [sops](https://github.com/getsops/sops) にAPI Tokenを保存して [terraform-provider-sops](https://github.com/carlpett/terraform-provider-sops) 経由で入れる。 ```terraform terraform { required_providers { sops = { source = "carlpett/sops" version = "~> 0.5" } cloudflare = { source = "cloudflare/cloudflare" version = "~> 5" } } } provider "sops" {} data "sops_file" "secret" { source_file = "./secrets.yaml" } provider "cloudflare" { api_token = data.sops_file.secret.data.cloudflare } ``` #### 3.2 レコード作成 {#3-dot-2-レコード作成} ```terraform locals { # for takeokunn.org takeokunn_org_zone_id = "xxx" } resource "cloudflare_dns_record" "a_records" { provider = cloudflare for_each = toset([ "185.199.108.153", "185.199.109.153", "185.199.110.153", "185.199.111.153" ]) zone_id = local.takeokunn_org_zone_id name = "takeokunn.org" content = each.key type = "A" ttl = 1 proxied = false } resource "cloudflare_dns_record" "cname_emacs" { zone_id = local.takeokunn_org_zone_id name = "emacs.takeokunn.org" content = "takeokunn.github.io" type = "CNAME" ttl = 1 proxied = false } ``` ### 4. 管理画面から確認する {#4-dot-管理画面から確認する} 正常に `terraform apply` されていることを確認する。 {{< figure src="/images/67154738-5226-46D6-80F3-959E0BD1F962.png" >}} ## 終わりに {#終わりに} お名前comからの脱却と、手元環境のTerraform化が進んできて非常によい。 - [NextDNSを導入した]({{< relref "20250402012730-setup_nextdns.md" >}}) - [Terraform GitHub Providerを導入した]({{< relref "20250402001222-setup_private_terraform_github_provider.md" >}}) ## Backlinks {#backlinks} - [private-terraformをHCP Terraformに移行した]({{< relref "20250507230034-use_private_terraform_cloud.md" >}}) -------------------------------------------------------------------------------- title: "NextDNSを導入した" date: "2025-04-02" -------------------------------------------------------------------------------- ## Introduction {#introduction} かねてから興味があった [NextDNS](https://nextdns.io/) が Terraform Provider と nix-darwin/nixos で設定できることを知り、せっかくなので導入してみた。 ## What is NextDNS? {#what-is-nextdns} 流行のDeep Reasearchの結果は以下Gist。 日本人でも使っている人がちらほらいる。 - [ほぼ無料で全てまとめて広告ブロックできるNextDNSとは](https://vpn-taizen.com/what_is_nextdns/) - [NextDNSを使ってみて広告ブロックが捗ったので紹介](https://mkusaka.com/2023/06/18/nextdns/) - [NextDNSで安全で快適なブラウジング](https://www.qam-web.com/?p=21061#google_vignette) ## 作業手順 {#作業手順} ### 1. アカウント作成してAPI Key発行 {#1-dot-アカウント作成してapi-key発行} 公式サイトからメールアドレスとパスワードで登録する。 管理画面からMFAを設定してAPI keyを発行する。 ### 2. Terraform設定 {#2-dot-terraform設定} ドキュメントを参考に画面上で設定できるものを宣言的に設定する。 #### 2.1 providerを設定 {#2-dot-1-providerを設定} [sops](https://github.com/getsops/sops) にAPI Tokenを保存して [terraform-provider-sops](https://github.com/carlpett/terraform-provider-sops) 経由で入れる。 ```terraform terraform { required_providers { sops = { source = "carlpett/sops" version = "~> 0.5" } nextdns = { source = "carbans/nextdns" version = "~> 0.2" } } } provider "sops" {} data "sops_file" "secret" { source_file = "./secrets.yaml" } provider "nextdns" { api_key = data.sops_file.secret.data.nextdns } ``` #### 2.2 各種設定 {#2-dot-2-各種設定} とりあえず有効にできるものは有効にした。 ```terraform resource "nextdns_profile" "main" { name = "Main Network" } resource "nextdns_security" "main" { profile_id = nextdns_profile.main.id ai_threat_detection = true crypto_jacking = true csam = true ddns = true dga = true dns_rebinding = true google_safe_browsing = true idn_homographs = true nrd = false parking = true threat_intelligence_feeds = true typo_squatting = true } resource "nextdns_privacy" "main" { profile_id = nextdns_profile.main.id disguised_trackers = true allow_affiliate = true } resource "nextdns_settings" "main" { profile_id = nextdns_profile.main.id web3 = true logs { enabled = true retention = "1 day" location = "us" privacy { log_clients_ip = true log_domains = true } } block_page { enabled = true } performance { ecs = true cache_boost = true cname_flattening = true } } ``` ### 3. Nix設定 {#3-dot-nix設定} #### 3.1 nix-darwin設定 {#3-dot-1-nix-darwin設定} nix-darwinはserviceで提供してくれているので有効にするだけ。 ```nix { services.nextdns = { enable = true; arguments = [ "-profile" "xxxx" ]; }; } ``` #### 3.2 NixOS設定 {#3-dot-2-nixos設定} NixOS公式が提供してくれているので同様に有効するだけ。 ### 4. 接続確認 {#4-dot-接続確認} あまりよくわかっていないが、以下を明示的にたたく必要があった。(要調査) ```console $ nix run nixpkgs#nextdns activate ``` 管理画面で有効になっていることを確認できればよい。 {{< figure src="/images/F20AAC35-AE8E-4165-BE3C-F39FDC46731A.png" >}} ついでにAndroidも設定をした。 ```text プライベート DNS Android 9 以降 1. 設定 → ネットワークとインターネット → 詳細設定 → プライベート DNS に移動します。 2.「プライベート DNS プロバイダのホスト名」オプションを選択します。 3. xxxx.dns.nextdns.io を入力し、「保存」を押します。 ``` ## 作業結果 {#作業結果} Webサイトへのアクセスが体感速くなったがあくまで体感。 もう少し運用してみて様子を見たい。 ## Backlinks {#backlinks} - [お名前comからCloudflareにドメイン移管してTerraformで管理する]({{< relref "20250403220528-migrate_onamae_to_cloudflare_and_managed_by_terraform.md" >}}) - [private-terraformをHCP Terraformに移行した]({{< relref "20250507230034-use_private_terraform_cloud.md" >}}) -------------------------------------------------------------------------------- title: "Terraform GitHub Providerを導入した" date: "2025-04-02" -------------------------------------------------------------------------------- ## Introduction {#introduction} 個人の運用でTerraform GitHub Providerを導入したのでメモしておく。 ## Motiviation {#motiviation} 長年ソフトウェアエンジニアをやってたらリポジトリが増えていく。特に最近のLLMの流行で検証用に作るリポジトリが顕著に増えている。 「Automatically delete head branchesは必ず有効にする」など、統一的な環境を宣言的にしたいという欲求があった。 また、SSH keysの設定など個人の設定も可能な限り宣言的に記述したい。 ## 作業手順 {#作業手順} こちらのproviderを使って導入する。 ### 0. Access Tokenを用意する {#0-dot-access-tokenを用意する} 「Personal access tokens > Fine-grained personal access tokens」で必要に応じて権限を付与してTokenを発行する。 ### 1. provider定義 {#1-dot-provider定義} [sops](https://github.com/getsops/sops) にGitHub API Tokenを保存して [terraform-provider-sops](https://github.com/carlpett/terraform-provider-sops) 経由で入れる。 ```terraform terraform { required_providers { sops = { source = "carlpett/sops" version = "~> 0.5" } github = { source = "integrations/github" version = "~> 6.0" } } } provider "sops" {} data "sops_file" "secret" { source_file = "./secrets.yaml" } provider "github" { token = data.sops_file.secret.data.github } ``` ### 2. ssh key定義 {#2-dot-ssh-key定義} [github_user_ssh_key](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/user_ssh_key) で定義できる。 ```terraform resource "github_user_ssh_key" "main" { title = "main" key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG9NCQp8KbNmdCQohUKzAJsNKe+Rz4IYDdthVX9Hzymi" } ``` ここで確認できる。 ### 3. gpg key定義 {#3-dot-gpg-key定義} [github_user_gpg_key](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/user_gpg_key) で定義できる。 ```terraform resource "github_user_gpg_key" "main" { armored_public_key = < ### 4. repository定義 {#4-dot-repository定義} [github_repository](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository) や [github_repository_topics](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_topics) あたりで定義できる。 ```terraform resource "github_repository" "blog" { name = "blog" description = "This is a Zettelkasten blog generated by org-roam and ox-hugo." visibility = "public" has_issues = true homepage_url = "https://www.takeokunn.org/" delete_branch_on_merge = true pages { build_type = "workflow" cname = "www.takeokunn.org" } } resource "github_repository_topics" "blog" { repository = github_repository.blog.name topics = ["blog", "hugo", "org-mode", "zettelkasten", "org-roam"] } ``` 上記の設定をすると [takeokunn/blog](https://github.com/takeokunn/blog) のようにtopicが反映される。 既存のリポジトリは `terraform import` で帳尻を合わせればよい ```console $ erraform import module.github.github_repository.blog blog ``` CI構築しているなら `sops` で秘密鍵を管理しつつ、 [github_actions_environment_secret](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_environment_secret) などで流すのも良さそう。 ## 作業結果 {#作業結果} メインで使っている個人リポジトリはすべてTerraform管理に移行した。 また、新規リポジトリもTerraform経由で宣言的に作成できることを確認した。 ## 終わりに {#終わりに} 使用感は非常に良く、移行も一晩で終わったので良かった。 TerraformはPrivateリポジトリで管理しており、ほかにも[ Cloudflare Provider ](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs)や [NextDNS Provider](https://registry.terraform.io/providers/carbans/nextdns/latest) を管理している。 外部サービスは可能な限りTerraformで管理できるようにしていきたい。 ## Backlinks {#backlinks} - [Typstで履歴書と職務経歴書をBuildする]({{< relref "20250619103829-build_cv_resume_with_typst.md" >}}) - [GitHub Actions内で署名付きCommitをする]({{< relref "20250518201409-signed_commit_in_github_actions.md" >}}) - [個人的Local環境のGit Branch運用について]({{< relref "20250518144557-local_git_branch_operation.md" >}}) - [お名前comからCloudflareにドメイン移管してTerraformで管理する]({{< relref "20250403220528-migrate_onamae_to_cloudflare_and_managed_by_terraform.md" >}}) - [GPG KeyのYubiKey運用をはじめた]({{< relref "20250514155803-yubikey_gpg_operation.md" >}}) - [private-terraformをHCP Terraformに移行した]({{< relref "20250507230034-use_private_terraform_cloud.md" >}}) -------------------------------------------------------------------------------- title: "@github/copilot-language-serverをNixで扱う時のメモ" date: "2025-02-27" -------------------------------------------------------------------------------- ## Introduction {#introduction} [@github/copilot-language-server](https://github.com/orgs/github/packages/npm/package/copilot-language-server) を [takeokunn/nixos-configuration](https://github.com/takeokunn/nixos-configuration) 上で扱えるようにしたのでメモしておく。 ## Background {#background} - [copilot.el](https://github.com/copilot-emacs/copilot.el) のバージョンが上がった - [@github/copilot-language-server](https://github.com/orgs/github/packages/npm/package/copilot-language-server) をnpmでインストールして使う必要があった - 次の要件を満たしたい - Nixでパッケージングしたい - Neovimなどのエディタでも汎用的に使えるようにしたい ## 対応手順 {#対応手順} ### 1. node2nixでパッケージングする {#1-dot-node2nixでパッケージングする} `node2nix/node-packages.json` に次のような記述をした。 ```json ["@github/copilot-language-server"] ``` 次のshell scriptを実行するとファイルが生成された。 - `node2nix/default.nix` - `node2nix/node-env.nix` - `node2nix/node-packages.nix` ```console $ nix-shell -p nodePackages.node2nix --command "node2nix -i ./node-packages.json -o node-packages.nix" ``` ### 2. 実行ファイルを取得する {#2-dot-実行ファイルを取得する} `node2nix/default.nix` をimportするとpackageが読み込めるようになった。 ```nix nodePkgs = pkgs.callPackage ../node2nix { inherit pkgs; }; ``` copilot-language-server は次のようなディレクトリ構造になっている。 ```console $ dust native/ 65M ├─┬ native/darwin-x64 65M │ └── native/darwin-x64/copilot-language-server 60M ├─┬ native/darwin-arm64 60M │ └── native/darwin-arm64/copilot-language-server 59M ├─┬ native/linux-x64 59M │ └── native/linux-x64/copilot-language-server 50M └─┬ native/win32-x64 50M └── native/win32-x64/copilot-language-server.exe ``` そこで、platformごとに対応表を作って `COPILOT_LANGUAGE_SERVER_PATH` にPATHを通してあげた。 ```nix { pkgs, nodePkgs }: let platforms = { "x86_64-linux" = "linux-amd64"; "aarch64-linux" = "linux-aarch64"; "x86_64-darwin" = "darwin-amd64"; "aarch64-darwin" = "darwin-arm64"; }; platform = builtins.getAttr pkgs.system platforms; in { home.packages = [ nodePkgs."@github/copilot-language-server" ]; programs.fish = { interactiveShellInit = '' set -gx COPILOT_LANGUAGE_SERVER_PATH ${ nodePkgs."@github/copilot-language-server" }/lib/node_modules/@github/copilot-language-server/native/${platform}/copilot-language-server ''; }; } ``` `2025/03/27(Mon)` 現在、次のような環境変数が定義されている。 ```console $ echo $COPILOT_LANGUAGE_SERVER_PATH /nix/store/314dbj3vqb5s0f37gszm948arm37layx-_at_github_slash_copilot-language-server-1.291.0/lib/node_modules/@github/copilot-language-server/native/darwin-arm64/copilot-language-server ``` ### 3. copilot.elにPATHを通す {#3-dot-copilot-dot-elにpathを通す} `exec-path-from-shell`: ```emacs-lisp (setopt exec-path-from-shell-variables '("PATH" "TERM" "SSH_AUTH_SOCK" "COPILOT_LANGUAGE_SERVER_PATH")) ``` `copilot.el`: ```emacs-lisp (setopt copilot-server-executable (getenv "COPILOT_LANGUAGE_SERVER_PATH")) ``` ## 終わりに {#終わりに} 問題なく使えるようになってうれしい。今後ともcopilotを使い倒していきたい。 ## Backlinks {#backlinks} - [node2nixの使い方]({{< relref "20250622133346-how_to_use_node2nix.md" >}}) -------------------------------------------------------------------------------- title: "2025年2月名古屋旅行" date: "2025-02-24" -------------------------------------------------------------------------------- ## Introduction {#introduction} 2025/2/21から2025/2/24までの名古屋旅行についての振り返りです。 名古屋上陸ははじめてでしたが、非常に楽しい旅行になりました。思い出したらまた追記するかもしれません。 ## 注意 {#注意} このブログはあくまで個人的な旅行記であり、「たけてぃのことを知っている or 物好きな人」向けの記事です。 帰りの高速バスの中で暇なので書いているくらいの温度感の記事なので真に受けないでください。 社会人として外面の良い活動はZennに後日纏めますので少々お待ちください。 ## スケジュール {#スケジュール} スケジュールはざっくり以下です。 今回の旅行も常に何かをしていたなという感覚があります。 | 開始日 | 開始時間 | 終了時間 | 項目 | |------|------|------|---------------------------| | 2/21(金) | 23:00 | 07:00 | 夜行バス 東京駅 → 名古屋駅 | | 2/22(土) | 9:30 | 19:00 | phpcon名古屋 | | | 19:00 | 21:00 | 懇親会 | | | 21:00 | 23:30 | 2次会 | | | 23:30 | 24:00 | 湯〜とぴあ宝 移動 | | 2/23(日) | 10:00 | 11:30 | 名古屋駅にてモーニング | | | 11:30 | 13:00 | 喫茶マウンテン with tadsan, あかつかさん | | | 13:30 | 16:30 | 東山動物園 | | | 17:00 | 19:00 | ひつまぶし in 栄 with tadsan | | | 19:00 | 21:00 | 栄散策 | | | 21:00 | 22:00 | 名古屋駅周辺散策 + 湯〜とぴあ宝 移動 | | 2/24(月) | 9:30 | 11:30 | 名古屋駅にてモーニングと定食を食べる | | | 11:30 | 12:00 | お土産買ってバス停に移動 | | | 12:00 | 18:00 | 高速バス 名古屋駅 → 東京駅 | ## ドメイン用語 {#ドメイン用語} 普段なにげなく使っているが、よくよく考えたら初見の人から見たら意味がわからない単語、それがドメイン用語なのです。 vim-jp slackだと「テキストエディタに目覚める」や「yasunori」など入門するのがむずかしい内容が多々あります。 俺ときんじょうさんとの間のドメイン用語はまだ入門しやすいと思います。 - 鳥貴族関連 - うぬぼれ - 焼き鳥 - エターナルホスピタリティ - 鳥貴族の運営会社名 - 刃牙で比喩する - e.g. 強者に生まれてツールに頼るのは女々しい - e.g. 「まだやるかい」と残業を心配する上司花山薫 - ノブレスオブリージュ - 何か仕事を達成した時に使う言葉 - きんじょうさんへ - ピーチ姫の「マリオへ」の改変 - 地方カンファレンスまで歩いて行く - 「方角」の話をしているので嘘ではないこともある また、僕だけが言ってる単語も多々あります。 - 無職の休日倶楽部 - 元同僚(A氏)が仕事辞めた後に「無職の休日倶楽部」とツイートしまくってたのに感銘を受けて使っている - 実際は残念ながら仕事をしている - 以前「無職には休日という概念がないよ」とTwitterでマジレスをされたことがある - 昼間はOL - Twitter(自称X)のプロフィール - 昔ネカマやってた時のプロフィールをそのまま使っている - 具体か抽象か(それhowなんよな) - そーだいさんの発表を揶揄する意図は一切ない ## 各イベントの感想 {#各イベントの感想} ### 高速バス {#高速バス} 夜行バスに乗るのは人生初でした。 「3列シートにしておけ」という初先輩方の助言により3列シートにしたのですが、普段よりは快適でした。 登壇スライドを作っていたのですが、目の前の客が無呼吸といびきと過呼吸を繰り返してて猛烈に心配になりました。 こういうのって自分じゃ気がつけないものなので強く生きてほしいです。 深夜のサービスエリアはエモいです。 バスの運ちゃんが疲れた顔しながら煙草吸ってるのとか非常にエモいですね。 帰りは昼間の高速バスで帰りました。 天気が良いのもあり富士山が映えますね。 たまたま一番後ろの席になったのですが、席を倒し放題なので爆睡しました。 ### phpcon名古屋 {#phpcon名古屋} こちらは別途Zennに「社会人として適切な文章」で書きますので少々お待ちを。 #### プロポーザル {#プロポーザル} phpcon東京でプロポーザルが全落ちしたことが確定した時、「プロポーザルを名古屋にそのままコピペで提出すればいいよ」というツイートが流れてきました。 なので、何も考えずにそのツイートを見た3分以内にコピペで提出をしました。 基本的に練馬から遠い地方phpconはプロポーザルが通ったら旅行ついでに行く、くらいの温度感で毎回出しています。 ありがたいことなのですが、なんかよくわからない間にプロポーザルが通りました。 #### 登壇準備 {#登壇準備} 登壇駆動ではなくそもそも仕事でやっていた内容なので、話したいことは夜行バスより前に箇条書きで書き出していました。 直近とにかく忙しくててんやわんやしていたのでスライド作成まで手を出せていませんでした。 結局スライドは夜行バスの中と開会までの間に名古屋駅のカフェで40枚作りきりました。 夜行バスはかなり揺れるので三半規管に自信ニキしか真似できなさそうです。 スピーカーノートを作りこんでおくと、実際の登壇で言い回しに困らなくなって良いというのを経験上知っているので、ひたすら他の人の登壇を聞きながら頭の中でメモをして練習をしていました。 スピーカーノートを作りこむのオススメですよ。 結局素振りをせずに本番を向かえたのでとにかく余裕がなかったです。 僕みたいな計画性のない奴は絶対に登壇駆動で何かをするべきではないです。 [NixでTypstをBuildしGitHub Pagesでホスティングする]({{< relref "../fleeting/20250126114218-build_and_deploy_typst_using_nix.md" >}}) にある通り、今回はtypstでスライドを生成しようかなと思ったのですが、夜行バスのネット環境が貧弱なこと、typstでスライドを作るのが初めてなこと、時間がないので失敗できないことを加味してGoogle Slideで作りました。 #### 本番 {#本番} Google Slideのスピーカーモードで開始ボタンを押し忘れて、今何分経過したのか分からない状態でしたが、そんな中本番はぴったり15分で話せてよかったです。 観客の反応も良かったし、ちょこちょこウケも取れて大満足でした。睡眠不足の深夜テンションでもなんとかなってほっとしました。 タイトルコールは平野綾さんでしたが、たけてぃと名前を呼ばれるなんてもうニッコニコですよ。オタク君としてはもう最高。 これだけでも名古屋に来てよかったなと思います。 個人的にはらきすたのこなたっぽくも呼ばれたかったので、来年も平野綾さんを呼んでほしいですね。 強欲でしょうか? いいえ誰でも。 今回はまきちゃんの登壇が裏番組でした。 げんえいさんは「たけてぃの発表はまー資料読めばいいからなー」といってまきちゃんの方に行ったらしく、ザブングル加藤みたいな顔になりました。 とはいえ、げんえいさんの立場になったら僕もそうしてたので何も言えませんでした。 #### 懇親会 {#懇親会} 懇親会は立食パーティ形式で料理が美味しかったです。 [Software Design3月号](https://zenn.dev/takeokunn/articles/20250211225710) を読みましたと言ってくれた方が多くて、こういう外部へのアウトプットをするのは中々良いものだなと思いました。 毎回固定の人と話しがちなのなんとかしたいなと思いつつ、新規の人にどう話しかけて良いのか毎回悩みますね。 本質的には僕個人の会話デッキの少なさが原因なので、交流のうまい人を参考にしながら上達していきたいものです。 今回は普段よりはかなり話せた方で自分としては健闘できました。 登壇すると話しかけられるというのは真なので、みんなどんどんプロポーザル出そうね。 #### 2次会 {#2次会} 2次会は名古屋の魚や土手煮など名古屋グルメが出てきました。 特に02さんと初対面の方(名前を存じ上げない)と3人で盛り上がりました。 あまりにも初対面の方が言ってることが共感しすぎて「そうそうそう」と言い続けるbotと化していました。 やはり俺達はちゃんと打席に立って全打席でホームランを打つ覚悟でいなくてはいけないし、技術のトレンドをちゃんと追いかけていきたいし、Howを極めていきたいという気持ちになりました。 今年のphpcon東京に来るっぽいのでおっさんとまた飲みに行きたいです。是非誘ってください。 流石に睡眠不足なのもあって3次会には参加しませんでした。 ### 湯〜とぴあ宝(宿) {#湯-とぴあ宝--宿} 全国30人のたけてぃゴミ宿レポートを楽しみにしている皆さんには悲報です。 湯〜とぴあ宝はめちゃめちゃ良かったです。 当初はドヤ街にある一泊1600円の宿に泊まる予定だったのですが、直前に予約しようとしたのもあって取れませんでした。 旅行ソムリエのぷぷさんに「湯〜とぴあ宝いいよ」と言われて直前の火曜日に急いでバスと一緒に予約しました。 内観は非常に綺麗でお風呂も沢山あり漫画コーナーや自販機や子供向けゲームセンターなども充実している非常に良い健康ランドでした。 笠寺駅は名古屋駅から30分程度で着くのでそこも魅力です。 プレミアムプランは宿泊プランで、カーテンで区切られた準個室のような空間でした。 2人分がギリ横になれるかくらいの広さですが、温度湿度も管理されており、快適に睡眠できました。 値段は6500円で僕が普段泊まる宿の2倍くらいの料金です。 サウナは4種類、水風呂は15℃とちょうど良く、椅子やベッドも充実しておりサウナーとしては大満足でした。 - フィンランド風サウナ - 竹炭ドライサウナ - ミストサウナ - 冷房室 ### 喫茶マウンテン {#喫茶マウンテン} [噂の喫茶マウンテン](https://ja.wikipedia.org/wiki/%E5%96%AB%E8%8C%B6%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%86%E3%83%B3)にtadsanとあかつかさんと行ってきました。 名古屋市内から1時間以内につく距離でそこそこ遠かったです。 店内はレトロな雰囲気で非常に趣があって良かったです。 メニューも「いかすみ」や「妹のラムネ」など謎なものが多かったです。 スイーツパスタを食べはじることを「登山」、食べれないことを「遭難」と言うらしいのですが、秒速で遭難しました。 こんなに美味しくないものを食べたのは久し振りでした。 パスタの温かさで生クリームが微妙に溶けている状態も美味しくなさすぎるし想像の5000倍不味かったです。 一旦口に入れた時に「これはやばい、はやく飲み込め」と脳は命令しているのに「これを受け入れる訳にはいかない」と胃が拒絶してる時間が長くあり、結果口の中に美味しくないものが残り続けて地獄でした。 結果吐くよりはマシかということで半分以上残して遭難しました。すみません。 一緒に行った2人はなんとか完食していて、自分が食に対して保守的なんだなとあらためて思いました。 tadsanに関しては黙々と食べてて怖かったです。 個人的には最悪な気分だったのですが、飲み会のスベらないネタが手に入ってそういう意味では美味いですね。 スイーツパスタがやばいだけで普通のメニューは美味しそうだったのでレトロ喫茶が好きな人は是非普通のメニューを頼むといいです。 ### 東山動物園 {#東山動物園} 喫茶マウンテンの近くに東山動物園があったので折角だしということで行きました。 東山動物園は動物の種類がとにかく多く、兎に角広いです。 13:30頃に着いて閉園が16:30頃まで3時間くらいひたすら歩きまわっていました。 理想は奥にある植物園まで行きたかったのですが流石に時間がなかったです。 僕はそういう星の元に生まれてきたのかこういうオモシロイベントが起こるんですよね。なんでなんですかね。 ### 名古屋駅/栄駅 周辺散策 {#名古屋駅-栄駅-周辺散策} 知らない街についた時は一通り練り歩いて街の形を把握するのが好きなのですが、名古屋駅も栄駅もとにかく広くて長時間歩く必要がありました。 そもそも愛知のこと知らなさすぎて栄と名古屋の位置関係を知らなかったのですが、数駅違いでめちゃくちゃ近いんですね。 愛知県民的には名古屋駅の方が都会だと思っているらしいですね。 夜の名古屋駅は雰囲気ありますね。 栄のテレビ塔前がまんま札幌でデジャブ感を感じてました。 夜の栄でいつものごとく不動産営業にからまれました。 流石天下のTOYOTA様のお膝元、自動車関係かどうかというので条件分岐が走るんですね。 営業のおにーちゃんが不吉なことを言っててひやっとしました。 ちなみに前回絡まれたのは福岡でした。 ## 名古屋グルメ {#名古屋グルメ} ### 2/22(土) {#2-22--土} 夜行バスから降りで一番最初にきしめんを食べました。 朝一に飲むビールは最高で体の細胞一つ一つに染み渡りました。 [きしめええええええええええええええええええええええええええええええええん](https://dic.nicovideo.jp/a/%E3%81%8D%E3%81%97%E3%82%81%E3%82%93) なんて今時知ってる人は何人いるんでしょうかね? 元のゲームをやってみたいと思い早n年ですが、DirectXのバージョン的にも多分もう動かないでしょう。 昼はアンチボッチランチということで、phperの皆さんとエビフライを食べました。 エビフライが名古屋で有名ということは知らなかったです。 写真はないですが、懇親会と二次会では土手焼きと手羽先と海鮮を食べました。 ### 2/23(日) {#2-23--日} 朝はモーニングを食べました。 これが噂の小倉あんかーと思いながらクロワッサンに塗って食べながら優雅な朝を迎えました。 昼は東山動物園のスカイタワーの上で地元の白身魚を使ったアクアパッツァを食べました。 もうちょっと白身魚に塩ふった方が味の境界が出て美味しくなりそうだなと思いつつ美味しかったので大満足です。 直前に食べた喫茶モンブランのダメージを緩和できました。 夜はtadsanと栄でひつまぶしを食べました。最高に美味しかったです。 ### 2/24(月) {#2-24--月} 昨日に引続きモーニングを食べました。 今回はプレーンなモーニングを楽しもうと、トーストと茹で卵のみのセットにしました。 よく考えたら手羽先も土手煮も味噌カツも飲み会のつまみ程度にしか食べれてなく、このまま東京に帰るのは勿体なさすぎるということで、モーニングの直後に名古屋ごちそう定食を食べました。 味噌カツもっと食べたかったです。 ## 終わりに {#終わりに} 当初の名古屋の期待値は死ぬほど低かったんですが、こんなに名古屋旅行楽しいとは思いませんでした。 かなり名古屋観光できたなと思いつつ、結局名古屋城とすがきやに行けなかったのが心残りです。 名古屋は練馬から遠いけどまた行きたいですね。 名古屋民はもっと自信を持って名古屋を語ってください。 「名古屋の魅力? [ないんだなそれが](https://dic.pixiv.net/a/%E3%81%AA%E3%81%84%E3%82%93%E3%81%A0%E3%81%AA%E3%80%81%E3%81%9D%E3%82%8C%E3%81%8C)」と言うのは栃木だから許されるのです。 ## 次遠征する時の自分へ {#次遠征する時の自分へ} まずはこちらの「次遠征する時の自分へ」を読みましょう。 - [PHPカンファレンス北海道2024に参加してきました]({{< relref "20240112063123-retrospective_phpcondo_2024.md" >}}) - [phpカンファレンス福岡2023に参加してきました]({{< relref "20230624093617-retrospective_phpconference_fukuoka_2023.md" >}}) 今回は雪が降っていた時間帯もあったが、福岡と比べて以下のdiffのみで十分でした。 - パーカー追加 - クロックス → 靴と靴下に変更 どちらにせよカバン一つで十分なことには変わりはないし、それ以上を持つのは持ちすぎなので反省してください。 湯〜とぴあ宝は館内着があったから、念の為持っていってた寝間着の半ズボンは不要でした。 高速バスを乗るならアイマスクを持っていくのもあり。 -------------------------------------------------------------------------------- title: "2025年2月 Neovimセットアップ" date: "2025-02-11" -------------------------------------------------------------------------------- ## Introduction {#introduction} Neovimに入門したいと思い早n年だったが、vim-jpの皆さんのおかげでようやくミニマムの設定が完了したのでブログにまとめておきます。 [VimとNeovimの使い分けと最小構成]({{< relref "20230209152709-using-vim-and-neovim-for-differenct-purposes-and-minimum-settings.md" >}}) のとおりVimは最小構成にしているので、Neovimはヘビーカスタマイズしていく方針です。 Neovimの設定はこちら。 [https://github.com/takeokunn/nixos-configuration/tree/main/home-manager/programs/neovim](https://github.com/takeokunn/nixos-configuration/tree/main/home-manager/programs/neovim) ## Setup {#setup} ### Neovim本体 {#neovim本体} Nix経由でインストールしています。 [https://github.com/takeokunn/nixos-configuration/blob/main/home-manager/programs/neovim/default.nix](https://github.com/takeokunn/nixos-configuration/blob/main/home-manager/programs/neovim/default.nix) `withNodeJs` や `withRuby` などはイマドキ使わない設定まらしいので `false` にしました。 ```nix { pkgs, sources }: { programs.neovim = { enable = true; withNodeJs = false; withRuby = false; withPython3 = false; plugins = import ./plugins { inherit pkgs sources; }; extraLuaConfig = builtins.readFile ./init.lua; }; } ``` ### Plugin Manager {#plugin-manager} VimのPlugin Managerは乱立しているようで、デファクトスタンダードはないらしいです。 私はNixユーザーなのでNixで管理することにしました。 また、各Pluginごとの設定もNix内に記述する方針にしました。 ```nix # 実際の例 { pkgs }: with pkgs.vimPlugins; [ { type = "lua"; plugin = dracula-nvim; config = '' vim.cmd[[colorscheme dracula]] ''; } ] ``` ### Terminal Emulator {#terminal-emulator} 「Terminal Emulatorにこだわらない」というこだわりを持っていたが、[Mac標準のTerminal.app](https://timingapp.com/terminal-time-tracking)だとうまく描画してくれない問題があった。 一応以下が候補だったが、NixOS環境でも安定的に動いてくれる、かつ友人のnatsukiumが使っている[Kitty](https://github.com/kovidgoyal/kitty)を採用しました。 - [WezTerm](https://wezterm.org/) - [Alacritty](https://github.com/alacritty/alacritty) - [Ghostty](https://github.com/ghostty-org/ghostty) 現状Kittyで特に困っていないしTerminal Emulatorに情熱がないのでこのまま続投するつもりです。 ## Config {#config} Neovimの設定はVim scriptではなくLuaを使うのがイマドキであり、多くのNeovim PluginはLuaで書かれているらしい。 それにならって自分もLuaで設定していくことにしました。 ### init.lua {#init-dot-lua} 基本的な設定は以下。 行番号はそもそも表示不要というのが最近の結論なので消しました。 ```lua vim.opt.number = false vim.opt.relativenumber = false vim.opt.encoding = "utf-8" vim.opt.fileencodings = "utf-8,euc-jp,cp932" vim.opt.clipboard:append("unnamed") vim.opt.backspace = "indent,eol,start" vim.opt.tabstop = 2 vim.opt.shiftwidth = 2 vim.opt.laststatus = 3 vim.opt.statusline = "%y" vim.opt.showmatch = true vim.opt.wrapscan = true vim.opt.hlsearch = true vim.opt.showcmd = true vim.opt.title = true vim.opt.foldenable = false vim.opt.swapfile = false vim.opt.expandtab = true vim.opt.splitbelow = true vim.opt.splitright = true vim.opt.incsearch = true vim.opt.ignorecase = true vim.opt.smartcase = true vim.opt.termguicolors = true ``` 簡単なkeymapは以下。 `mapleader` をスペースに割り当てている人が多数派みたいですが、個人的には `,` が昔から好きです。 ```lua vim.g.mapleader = "," vim.keymap.set("n", "/", "/\\v", { remap = false }) vim.keymap.set("n", "U", "", { remap = false }) vim.keymap.set("n", "", "V", { remap = false }) vim.keymap.set("n", "", "nohlsearch", { remap = false }) vim.keymap.set("i", "", "") ``` windowやbufferの切り替えは簡単な設定をしています。 ```lua -- window keymap vim.keymap.set("n", "sj", "j", { noremap = true, silent = true }) vim.keymap.set("n", "sk", "k", { noremap = true, silent = true }) vim.keymap.set("n", "sl", "l", { noremap = true, silent = true }) vim.keymap.set("n", "sh", "h", { noremap = true, silent = true }) vim.keymap.set("n", "sJ", "J", { noremap = true, silent = true }) vim.keymap.set("n", "sK", "K", { noremap = true, silent = true }) vim.keymap.set("n", "sL", "L", { noremap = true, silent = true }) vim.keymap.set("n", "sH", "H", { noremap = true, silent = true }) vim.keymap.set("n", "sw", "w", { noremap = true, silent = true }) -- buffer keymap vim.keymap.set("n", "sp", ":bp", { noremap = true, silent = true }) vim.keymap.set("n", "sn", ":bn", { noremap = true, silent = true }) ``` ### package {#package} 項目分けは雑なのでもう少しpluginが増えたら考え直します。 #### basic {#basic} - [hop-nvim](https://github.com/smoka7/hop.nvim) - easymotionみたいなplugin - Emacsでいう所の[avy](https://github.com/abo-abo/avy) - [vim-bracketed-paste](https://github.com/ConradIrwin/vim-bracketed-paste) - クリップボード用 - [vim-fern](https://github.com/lambdalisue/vim-fern) - ありすえwareのファイラ - Emacsでいう所の[neotree](https://www.google.com/search?q=neotree+emacs&oq=neotree&gs_lcrp=EgZjaHJvbWUqBwgBEAAYgAQyBggAEEUYOTIHCAEQABiABDIHCAIQABiABDIHCAMQABiABDIHCAQQABiABDIGCAUQABgeMgYIBhAAGB4yBggHEAAYHjIGCAgQABgeMgYICRAAGB7SAQgyMzY4ajBqNKgCALACAA&sourceid=chrome&ie=UTF-8) - [vim-nerdfont](https://github.com/lambdalisue/vim-nerdfont) - nerdfontを扱えるようにするやつ - [vim-fern-renderer-nerdfont](https://github.com/lambdalisue/vim-fern-renderer-nerdfont) - ファイラにアイコンを表示するやつ - [undotree](https://github.com/mbbill/undotree) - undo historyを可視化するやつ - Emacsでいう所の[undo-fu](https://github.com/emacsmirror/undo-fu) - [vim-textobj-entire](https://github.com/kana/vim-textobj-entire) - buffer全体をテキストオブジェクトにするやつ - [vimdoc-ja](https://github.com/vim-jp/vimdoc-ja) - 日本語help - [auto-pairs](https://github.com/jiangmiao/auto-pairs) - Emacsでいう所のelectric-pair-modeみたいなやつ - [rainbow](https://github.com/luochen1990/rainbow/) - 括弧の対応をハイライトしてくれるやつ - Emacsでいう所の[rainbow-delimiters](https://github.com/Fanael/rainbow-delimiters) - [vim-sandwich](https://github.com/machakann/vim-sandwich) - 括弧の操作を簡単にできるやつ - Emacsでいう所の[puni-mode](https://github.com/AmaiKinono/puni) - [denops-vim](https://github.com/vim-denops/denops.vim) - Deno製plugin用 ざっくりこのあたりを入れています。 #### Git {#git} - [gitsigns-nvim](https://github.com/lewis6991/gitsigns.nvim) - 変更を可視化してくれるやつ - Emacsでいう所の[git-gutter](https://github.com/emacsorphanage/git-gutter) - [gin](https://github.com/lambdalisue/vim-gin) - ありすえwareのGit clinet - Emacsでいう所の[magit](https://github.com/magit/magit) そこまで使っていないです。 Git操作はTerminalでもよいのかなという気持ちになってます。 #### language {#language} - [orgmode](https://github.com/jceb/vim-orgmode) - [vim-markdown](https://github.com/preservim/vim-markdown) 基本的にTree-sitterがhighlightしてくれるので特別入れる必要がなかったです。 #### lsp {#lsp} - [nvim-cmp](https://github.com/hrsh7th/nvim-cmp) - [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) - Emacsでいう所の[lsp-mode](https://github.com/emacs-lsp/lsp-mode) - [lspsaga-nvim](https://github.com/nvimdev/lspsaga.nvim) - Emacsでいう所の[lsp-ui](https://github.com/emacs-lsp/lsp-ui) ざっくりとした設定は以下。最低限満足するUIになってくれました。 `nvim-cmp`: ```lua local cmp = require("cmp") cmp.setup({ snippet = { expand = function(args) vim.fn["vsnip#anonymous"](args.body) end, }, sources = { { name = "nvim_lsp" }, { name = "path" }, }, mapping = cmp.mapping.preset.insert({ [""] = cmp.mapping.select_prev_item(), [""] = cmp.mapping.select_next_item(), [''] = cmp.mapping.complete(), [''] = cmp.mapping.abort(), [""] = cmp.mapping.confirm { select = true }, }), experimental = { ghost_text = true, }, }) ``` `nvim-lspconfig`: ```lua local lspconfig = require('lspconfig') vim.keymap.set('n', 'gd', 'lua vim.lsp.buf.definition()', { silent = true, buffer = buffer }) if vim.fn.executable('nil') == 1 then lspconfig.nil_ls.setup { settings = { ['nil'] = { formatting = { command = { 'nixfmt' } } } } } end if vim.fn.executable('typescript-language-server') == 1 then lspconfig.ts_ls.setup { } end if vim.fn.executable('intelephense') == 1 then lspconfig.intelephense.setup { } end ``` `lspsaga-nvim`: ```lua require('lspsaga').setup({ code_action = { extend_gitsigns = true, }, finder = { max_height = 0.7, left_width = 0.3, right_width = 0.6, keys = { shuttle = "w", toggle_or_open = "" } }, lightbulb = { enable = false, } }) vim.keymap.set('n', 'K', 'Lspsaga hover_doc') vim.keymap.set({ 'n', 'i' }, '', "Lspsaga rename", opts) vim.keymap.set('n', '', "Lspsaga finder def+ref", opts) vim.keymap.set('n', '', "Lspsaga peek_definition", opts) vim.keymap.set('n', '', "Lspsaga diagnostic_jump_next", opts) vim.keymap.set('n', '', "Lspsaga diagnostic_jump_prev", opts) ``` #### skk {#skk} - [skkeleton](https://github.com/vim-skk/skkeleton) - Emacsでいう所の[ddskk](https://github.com/skk-dev/ddskk) - [skkeleton-azik-kanatable](https://github.com/kei-s16/skkeleton-azik-kanatable) 簡易的な設定とskkservへの接続、AZIKの有効化をしました。 `skkeleton#register_kanatable` で独自のかなテーブルを定義できるらしいです。 `skkeleton`: ```lua vim.fn['skkeleton#config']({ eggLikeNewline = true, keepState = true, sources = { "skk_server" } }) vim.keymap.set({ 'i', 'c' }, '', '(skkeleton-toggle)', { silent = true }) ``` `skkeleton-azik-kanatable`: ```lua vim.fn['skkeleton#azik#add_table']('us') vim.fn['skkeleton#config']({ kanaTable = 'azik' }) vim.call("skkeleton#register_kanatable", "azik", { ss = { "せい" }, }) ``` #### telescope {#telescope} - [telescope-nvim](https://github.com/nvim-telescope/telescope.nvim) - [telescope-ui-select-nvim](https://github.com/nvim-telescope/telescope-ui-select.nvim) - [telescope-file-browser-nvim](https://github.com/nvim-telescope/telescope-file-browser.nvim) - [telescope-fzf-native-nvim](https://github.com/nvim-telescope/telescope-fzf-native.nvim) telescopeは簡単に導入できるということで導入しました。 Emacsでいう所の、というのは説明が難しいですね。 tomoyaさんの [Emacsの次世代ミニバッファ補完UI](https://blog.tomoya.dev/posts/a-new-wave-has-arrived-at-emacs/) あたりが参考になりそうです。 `` はデフォルトでマッピングされてないのでこれで問題ないようです。 ```lua require('telescope').setup { extensions = { fzf = { fuzzy = true, override_generic_sorter = true, override_file_sorter = true, case_mode = "smart_case", } } } local builtin = require('telescope.builtin') vim.keymap.set('n', 'f', builtin.git_files, { desc = 'Telescope find git files' }) vim.keymap.set('n', 'o', builtin.current_buffer_fuzzy_find, { desc = 'Telescope buffer fuzzy find' }) vim.keymap.set('n', 'g', builtin.live_grep, { desc = 'Telescope live grep' }) vim.keymap.set('n', 'b', builtin.buffers, { desc = 'Telescope buffers' }) vim.keymap.set('n', 'h', builtin.help_tags, { desc = 'Telescope help tags' }) ``` #### themes {#themes} - [dracula-nvim](https://github.com/Mofiqul/dracula.nvim) - Emacsでいう所の[doom-theme](https://github.com/doomemacs/themes) - [lualine-nvim](https://github.com/nvim-lualine/lualine.nvim) - Emacsでいう所の[doom-modeline](https://github.com/seagle0128/doom-modeline) すべてのカラーテーマをdraculaに寄せているので導入しました。 また、modelpineはlualine-nvimがイマドキらしいです。 ## 終わりに {#終わりに} vim-jpの皆さん(特にkuuさん、Kento Ogataさん、Shougoさん、こまもかくん、おもちあいす)のおかげで無事日常生活できるくらいの設定になりました。 もう少しVimに慣れたらDark Poweredなpackageを試していくつもりです。 -------------------------------------------------------------------------------- title: "org-nix-shell入門" date: "2025-02-08" -------------------------------------------------------------------------------- ## Introduction {#introduction} org-nix-shellはOrg関連パッケージの中でもキラーアプリケーションと呼べるもので、個人的に今熱いパッケージのひとつです。 簡単な使い方と実際のユースケースを紹介していきます。 ## インストール {#インストール} - GitHub: - melpa: Melpa経由で入れてもよいし、GitHub経由で直接インストールしてもよい。 次のように設定するだけで機能してくれる。 ```emacs-lisp (require 'org-nix-shell) (with-eval-after-load 'org (add-hook 'org-mode-hook #'org-nix-shell-mode)) ``` ## 使い方 {#使い方} ### Basic {#basic} 次のようにNixを定義して `:nix-shell` で読み込む。 ```org #+name: tmp-shell #+BEGIN_SRC nix :noweb yes { pkgs ? import { } }: pkgs.mkShell { buildInputs = with pkgs; [ php ]; } #+END_SRC #+begin_src shell :nix-shell tmp-shell :results output php --version #+end_src ``` また、次のURLから古いバージョンを取ってきて実行することも可能。 ```org #+name: php74-shell #+BEGIN_SRC nix :noweb yes let url = "https://github.com/NixOS/nixpkgs/archive/4426104c8c900fbe048c33a0e6f68a006235ac50.tar.gz"; pkgs = import (builtins.fetchTarball { inherit url; }) { system = "<>"; }; in pkgs.mkShell { buildInputs = with pkgs; [ php74 ]; } #+END_SRC #+begin_src php :nix-shell php74-shell :exports both echo phpversion(); #+end_src #+RESULTS: : 7.4.4 ``` property指定もできる。 Buffer Scope: ```org #+property: header-args: :nix-shell ``` Subtree Scope: ```org * sample header :PROPERTIES: :header-args: :nix-shell :END: ``` ### Adviced {#adviced} 複数の処理系を定義しておけば、wandboxのようなことも実現可能になる。 ## 所感 {#所感} 必要なものを明示的にインストールして実行できるのでポータビリティが非常に高い。 ブログや運用手順書との相性が非常に良く、文芸的プログラミングのレベルをひとつ上にレベルアップすることが可能になった。 ## Backlinks {#backlinks} - [credential管理をorg-encryptからpassword-storeに移行した]({{< relref "20250414001634-migrate_credential_manager_to_password_store.md" >}}) -------------------------------------------------------------------------------- title: "Laravel LazyCollection in depth" date: "2025-02-04" -------------------------------------------------------------------------------- ## Introduction {#introduction} LaravelのLazyCollectionは巨大なデータを扱う時に便利であるという話は巷で良く聞くが、実際の所どういうときに便利なのか、内部的にはどうなっているのかという情報はあまり聞かない。 LazyCollection自体のソースコードを読んでかなり理解できたのでメモしておく。 なお、そもそもの使い方については [LazyCollection備忘録 - Qiita](https://qiita.com/yhosok/items/a95ae800970124fb503a) を読んでいる前提で話を進める。 ## 前提 {#前提} 次のバージョンを想定しています。 - laravel: 10.0 - php: 8.2 LazyCollectionのコードはこちら。 ## LazyCollectionについて {#lazycollectionについて} ### Basic {#basic} #### LazyCollection#sourceについて {#lazycollection-sourceについて} 基本的には [LazyCollection#source](https://github.com/laravel/framework/blob/10.x/src/Illuminate/Collections/LazyCollection.php#L31-L36) にClosureを入れてどう引き回すか、どのタイミングでClosureを発行するのかということを制御している。 ```php /** * The source from which to generate items. * * @var (Closure(): \Generator)|static|array */ public $source; ``` LazyCollectionのコンストラクタの引数(or `LazyCollection#make`)には `Array|null` か `Closure(Generator)` を渡すことが可能で、 `LazyCollection#source` に代入する。 ```php > \Illuminate\Support\LazyCollection::make(function () { for ($i = 1; $i <= 10000000; $i++) yield $i; })->source = Closure() {#5542 <80>2} > \Illuminate\Support\LazyCollection::make([1, 2 ,3])->source; = [1, 2, 3] > \Illuminate\Support\LazyCollection::make()->source = [] // arrayにcastされる > \Illuminate\Support\LazyCollection::make(1)->source = [ 1 ] ``` #### 基本的な関数 {#基本的な関数} ここではmapを例に上げる。 map内では `new static(LazyCollection)` して引数にgeneratorを書いている。 ```php /** * Run a map over each of the items. * * @template TMapValue * * @param callable(TValue, TKey): TMapValue $callback * @return static */ public function map(callable $callback) { return new static(function () use ($callback) { foreach ($this as $key => $value) { yield $key => $callback($value, $key); } }); } ``` 次の簡単なサンプルでは、map実行時にyieldをネストしたような形の `Closure` が定義され、新しいLazyCollectionを作りつつsourceに代入される。 ```php // make時 $lazy = \Illuminate\Support\LazyCollection::make([1, 2 ,3]); // sourceはこのような形になる($originalSource) $lazy->source = function () use ($source) { yield from $source; // 1, 2, 3の順番でreturnする }; // mapを実行 $lazy->map(fn ($elm) => $elm + 1); // sourceはこのような形になる // $callback = fn ($elm) => $elm + 1 $this->source = function () use ($callback) { foreach ($originalSource() as $key => $value) { yield $key => $callback($value, $key); } }; // foreachでloopできる $lazyMap = \Illuminate\Support\LazyCollection::make([1, 2 ,3])->map(fn ($elm) => $elm + 1); foreach($lazyMap as $value) { echo $value; // 2, 3, 4が出力される } ``` #### 評価 {#評価} `all` などを実行するとGeneratorで定義されていたものを発行することになる。 ```php > \Illuminate\Support\LazyCollection::make([1, 2 , 3]) ->map(fn ($elm) => $elm + 1) ->all(); = 9 ``` `->all()` でGeneratorを [iterator_to_array](https://www.php.net/manual/ja/function.iterator-to-array.php) で配列にする処理が書かれている。 ```php /** * Get all items in the enumerable. * * @return array */ public function all() { if (is_array($this->source)) { return $this->source; } return iterator_to_array($this->getIterator()); } ``` phpのforeachでloopできるのは、LazyCollection内で[IteratorAggregate interface](https://www.php.net/manual/ja/class.iteratoraggregate.php)を実装しているから。 - `IteratorAggregate` の場合、 `$source` 自体に定義された `getIterator` を実行する - `is_array` の場合、ArrayIteratorを返す - `is_callable` の場合、Generatorを返す ```php /** * Make an iterator from the given source. * * @template TIteratorKey of array-key * @template TIteratorValue * * @param \IteratorAggregate|array|(callable(): \Generator) $source * @return \Traversable */ protected function makeIterator($source) { if ($source instanceof IteratorAggregate) { return $source->getIterator(); } if (is_array($source)) { return new ArrayIterator($source); } if (is_callable($source)) { $maybeTraversable = $source(); return $maybeTraversable instanceof Traversable ? $maybeTraversable : new ArrayIterator(Arr::wrap($maybeTraversable)); } return new ArrayIterator((array) $source); } /** * Get the values iterator. * * @return \Traversable */ public function getIterator(): Traversable { return $this->makeIterator($this->source); } ``` ### Advanced {#advanced} #### 遅延評価関数とそれ以外の違い {#遅延評価関数とそれ以外の違い} `return new static` のものは遅延評価、それ以外のものは即時評価対象。 `sum` や `avg` などすべてを評価したうえで実行しないと結果が得られないものも即時評価対象。 ```php // 遅延評価 public static function make($items = []) { return new static($items); } // 即時評価 public function all() { if (is_array($this->source)) { return $this->source; } return iterator_to_array($this->getIterator()); } ``` #### passthru関数 {#passthru関数} 既存の `Collection` に生えているメソッドを実行したうえで `LazyCollection` にしたい場合は `LazyCollection#passthru` を使う必要がある。 ```php /** * Pass this lazy collection through a method on the collection class. * * @param string $method * @param array $params * @return static */ protected function passthru($method, array $params) { return new static(function () use ($method, $params) { yield from $this->collect()->$method(...$params); }); } ``` 内部的にはかなり使われているが、いったん `Collection` に変換する過程で当然メモリ上に載ってしまう。 ```php public function sort($callback = null) { return $this->passthru('sort', func_get_args()); } ``` ## 想定QA {#想定qa} ### Q. LazyCollection作成時の引数に巨大な配列を渡した場合はどうなる? {#q-dot-lazycollection作成時の引数に巨大な配列を渡した場合はどうなる} 次のように10000000件の配列を代入した場合は当然 `LazyCollection#source` に10000000件の配列が代入される。 ```php \Illuminate\Support\LazyCollection::make(range(1, 10000000)); // 10000000件の配列を代入する ``` ただ、その後の処理はGeneratorで処理が進むのでメモリ確保としては最初だけになる。 ### Q. LazyCollectionを使う時の注意事項はある? {#q-dot-lazycollectionを使う時の注意事項はある} 使う関数が `return new static` を返しているか、 `LazyCollection#source` の評価タイミングがいつなのかを常に意識する必要がある。 このあたりのケアが面倒なので件数が少ない時は `Collection` を素直に使うのでも良さそう。 逆にいうと、そのあたりをちゃんとケアできる自信があるなら `LazyCollection` ですべて処理しても良さそう。 ## 終わりに {#終わりに} 職場で扱うデータ量が多いと學びが多い。 ## Backlinks {#backlinks} - [php-srcとyieldについて調査メモ]({{< relref "20250625115611-yield_in_php_src.md" >}}) -------------------------------------------------------------------------------- title: "Nixでdap-modeがエラーになった時のメモ" date: "2025-02-03" -------------------------------------------------------------------------------- ## Introduction {#introduction} 2025/02/02(Sun)に `nix flake update` で最新にしてBuildしたらエラーで落ちるようになったのでメモ。 ## 現象 {#現象} 1. 2025/01/31に [dapui.elがdap-ui.elにrename](https://github.com/emacs-lsp/dap-mode/commit/438679755e880f2a662a63bc04da9e843257e248) される 2. [dap-mode 20250131.1624](https://melpa.org/#/dap-mode) に反映される 3. 2025/02/02の [nix flake update nixpkgsのdap-modeのpreBuild](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/editors/emacs/elisp-packages/melpa-packages.nix#L984-L991) でdapui.elがなくて落ちるようになった ## 最小構成と解決策 {#最小構成と解決策} 次のように `preBuild` をoverlaysに追加すればBuildできるようになった。 ```nix { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; emacs-overlay.url = "github:nix-community/emacs-overlay"; }; outputs = { self, nixpkgs, emacs-overlay, ... }: let system = "aarch64-darwin"; pkgs = import nixpkgs { inherit system; overlays = [ # ここを追加 (self: super: { emacsPackages = super.emacsPackages // { dap-mode = super.emacsPackages.dap-mode.overrideAttrs (old: { preBuild = null; }); }; }) emacs-overlay.overlay ]; }; in { defaultPackage.${system} = pkgs.emacsPackages.dap-mode; }; } ``` また、 `emacs-overlay` 使う場合は `pkgs.emacsWithPackagesFromPackageRequires` にoverlayを追加すればBuildできるようになる。 ```nix { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; emacs-overlay.url = "github:nix-community/emacs-overlay"; }; outputs = { self, nixpkgs, emacs-overlay, ... }: let system = "aarch64-darwin"; pkgs = import nixpkgs { inherit system; overlays = [ emacs-overlay.overlay ]; }; in { defaultPackage.${system} = pkgs.emacsWithPackagesFromPackageRequires { packageElisp = builtins.toFile "empty.el" ""; extraEmacsPackages = epkgs: with epkgs; [ dap-mode ]; override = final: prev: { dap-mode = prev.melpaPackages.dap-mode.overrideAttrs (old: { preBuild = null; }); }; }; }; } ``` 自分のhome-managerには次のように追記した。 ## 終わりに {#終わりに} 調査に協力してくれたnatsukiumに感謝。 -------------------------------------------------------------------------------- title: "「Lispが安定してることがアピールポイントか」に対しての考察" date: "2025-01-29" -------------------------------------------------------------------------------- ## 背景 {#背景} [vim-jpラジオのEmacs回に出演しました - Zenn](https://zenn.dev/takeokunn/articles/20250126133908) を書いたのだが、conao3から「n年前のコードを動かすためにはn年成長しなければよい、そこはアピールポイントにならないという気持ちではある」という指摘を受けた。 その過程でvim-jpの `#random` チャンネルで議論になった。 人によってかなり前提や置かれている状況が違うので、自分が想定していた条件や「Lispが安定している」についての理解と主張を文書という形でまとめておく。 ## 免責事項 {#免責事項} この記事は個人ブログに書いているので、あくまで個人の理解や見解をまとめている。 その為、技術的に間違えていることが書かれている可能性があるので要注意。 | platform | 想定読者 | 目的 | |---------------|------|--------| | Zenn | 不特定多数 | 対外的な宣伝目的 | | takeokunn.org | 特定少数 | 個人的なメモ | また、特に断りがなければ以下を想像しながら書いている。 本当はGoやRustも含めて書きたかったが経験が浅いので今回は除外する。 - Lisp: Common Lisp - 通常の言語: PHP, NodeJS ## 対象箇所 {#対象箇所} [こまもかさんからのお便り](https://zenn.dev/takeokunn/articles/20250126133908#%E3%81%93%E3%81%BE%E3%82%82%E3%81%8B%E3%81%95%E3%82%93%E3%81%8B%E3%82%89%E3%81%AE%E3%81%8A%E4%BE%BF%E3%82%8A) から抜粋。 > Q. > Emacsといえば設定をEmacs Lispという専用のLispで行ったり、他のエディタよりLisp系言語のサポートが手厚い事が有名だと思います。 > かく言う僕もCommon Lispを書くためにEmacsを始めたのですが、皆さんの思うLispの魅力などについてお聞きしたいです。 > > A. > Lispの魅力についての外観をつかみたいなら [ハッカーと画家 コンピュータ時代の創造者たち](https://www.amazon.co.jp/%E3%83%8F%E3%83%83%E3%82%AB%E3%83%BC%E3%81%A8%E7%94%BB%E5%AE%B6-%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF%E6%99%82%E4%BB%A3%E3%81%AE%E5%89%B5%E9%80%A0%E8%80%85%E3%81%9F%E3%81%A1-%E3%83%9D%E3%83%BC%E3%83%AB-%E3%82%B0%E3%83%AC%E3%82%A2%E3%83%A0/dp/4274065979) や [Land of Lisp](https://www.oreilly.co.jp/books/9784873115870/) や [A Road to Common Lisp](https://gist.github.com/y2q-actionman/49d7587912b2786eb68643afde6ca192) を読むことをお勧めします。 > Lispにもっと踏み込みたいなら [On Lisp](https://www.asahi-net.or.jp/~kc7k-nd/onlispjhtml/) や [LET OVER LAMBDA](https://www.amazon.co.jp/LET-OVER-LAMBDA-1-0-%E3%83%9B%E3%82%A4%E3%83%88/dp/4434133632) もお勧めです。 > > 僕個人としての魅力は、Lisp自体の言語仕様が変わることはなく、Lispの言語仕様の上にmacroや関数を積み上げているので30年前のコードベースも余裕で動くことです。 > ソフトウェアを開発していると後方非互換の回し車に常に追われており、頻繁なバージョンアップを余儀なくされます。 > phpプロジェクトは1年塩漬にされているとさまざまな依存関係のせいで動かないことが多々あります。 ## 当初の自分の主張 {#当初の自分の主張} プログラムを書くにはざっくり4種類の層がある。 - 言語自体の構文や処理系 - 処理系が提供している標準関数やpackage - 外部packageやフレームワーク - ユーザーが書いているコード 主観の各言語ごとの変化の度合のマトリックスは以下。(×→変化なし、○→変化量が多い) 一度書いたコードが未来の処理系でも過去の処理系でも動くかどうかということを想定している。 「言語自体の構文や処理系」を変更するとすべてを変更する必要があるように、ステップが下にいくにつれて影響度合いが大きくなる。 | | Lisp | NodeJS | PHP | |------------------------|------|--------|-----| | 言語自体の構文や処理系 | × | △ | △ | | 処理系が提供している標準関数やpackage | △ | △ | △ | | 外部packageやフレームワーク | △ | ◎ | ◎ | 実際のプロジェクトはほぼ次の2種類のパターンで構成されている。 Emacs Lispの場合はフレームワークまで含めてなのでBを指す。 - A. 言語自体の構文や処理系 + 処理系が提供している標準関数やpackage + ユーザーが書いているコード - B. 言語自体の構文や処理系 + 処理系が提供している標準関数やpackage + 外部packageやフレームワーク + ユーザーが書いているコード また、世の中にはお作法のよいプロジェクトと悪いプロジェクトの2種類がある。 作法のよいプロジェクトはインストール手順や依存バージョンが明瞭に明記されており、高確率でストレスなく入ってくれるものを想定している。 作法の悪いプロジェクトはドキュメントも何もない、何年もメンテナンスされていない、経験が浅い人が作ったようなものを想定している。 お作法が悪いものをA'とB'とすると、個人的な安定度合の感覚のマトリックスは以下。(×→安定しない、○→安定する) 安定度合とは、[Goの古いコードが動かなくなることはほぼない理由](https://zenn.dev/catatsuy/articles/fda1e42acad421) に書かれているような、長期間塩けっしても動いてくれるといったことを想定している。 | | Lisp | NodeJS | PHP | |----|------|--------|-----| | A | ◎ | ○ | ○ | | A' | ○ | △ | △ | | B | ◎ | ○ | ○ | | B' | △ | × | × | Common Lispで有名なマクロユーティリティ(外部package)の[alexandria](https://gitlab.common-lisp.net/alexandria/alexandria)は数十年コアコードに変化はないが、[ASDF](https://www.google.com/search?q=asdf+lisp&sca_esv=15ea5b57039dcf13&sxsrf=AHTn8zp7VwKCu0fTa5_AYtImnPRaLUXpfw%3A1738130722660&ei=IsWZZ6X-J6eP2roP4pDnsQc&ved=0ahUKEwill7DCoZqLAxWnh1YBHWLIOXYQ4dUDCBA&uact=5&oq=asdf+lisp&gs_lp=Egxnd3Mtd2l6LXNlcnAiCWFzZGYgbGlzcDIKECMYgAQYJxiKBTIEEAAYHjIIEAAYgAQYogQyBRAAGO8FMgUQABjvBTIIEAAYgAQYogQyBhAAGAgYHjIGEAAYCBgeSN0EUI0DWI0DcAF4AZABAJgBiQGgAYkBqgEDMC4xuAEDyAEA-AEBmAICoAKNAcICBxAjGLADGCfCAgoQABiwAxjWBBhHmAMA4gMFEgExICniAwUSATEgQIgGAZAGCpIHAzEuMaAHyAM&sclient=gws-wiz-serp)で簡単にインストールが可能。 [後方非互換の回し車から逃れる - A Road to Common Lisp](https://gist.github.com/y2q-actionman/49d7587912b2786eb68643afde6ca192#escaping-the-hamster-wheel-of-backwards-incompatibility) にも一節があり、この一節には自分の過去の業務経験からも共感できる。 > もしあなたが他の言語から来たのなら、あなたが言語実装やライブラリを「アップデート」して色々壊れる、ということがあったのではないでしょうか。あなたが10年前に書いた Ruby コードを、最新バージョンの Ruby で走らせようとしたら、おそらくアップデートにはある程度の努力が必要でしょう。私の現在の仕事は Scala なのですが、あるライブラリの最後の Github 上の更新が2,3年前だったなら、私はすぐに、私の側でかなりの量の変更をしないと動かないだろうな、と想定します。この 後方非互換の回し車 こそが、私達が毎日扱っているものであり、そして多くのモダンな言語における現実 なのです。モダンな言語には、確かに他の言語よりいいところもあるのですが。 > > Common Lisp を学ぶと、このようなことは通常ありません。この投稿の次の節で、私は1990年に書かれた本を推薦するつもりです。あなたはその本のコードを、変更することなく、先月にリリースされた Common Lisp 実装で走らせることが出来ます。 後方非互換の回し車 で何年もジョギングしないと、私は書いたコードが20年後も十分動くと期待できることがどれだけ 安心 かを言うことが出来なかったでしょう。 ## conao3の主張 {#conao3の主張} 「n年前のコードを動かすためにはn年成長しなければよい、そこはアピールポイントにならないという気持ちではある」やそれに続けての主張について列挙すると以下。 - そもそも10年間何も開発しなければよいだけなのでは - ユーザーが少ないからニーズが少ないだけなのでは - 特にEmacsの場合は破壊的変更を極端に避ける傾向があり、引数が増えるなどワークアラウント的な対応が多くなっている - 塩漬けについてはその環境を再現するということだけで達成できて、塩漬けしたものが現代のランタイムでそのまま動くというのは運がよいだけもしくはランタイムが単に変化してないだけ - Nixのようなエコシステムで実現可能なのでは - エコシステムの設計までされているGoやRustでもよいのでLispだからという訳ではないのでは ## 「conao3の主張」に対しての解答 {#conao3の主張-に対しての解答} ### そもそも10年間何も開発しなければよいだけなのでは {#そもそも10年間何も開発しなければよいだけなのでは} S式で自分で構文を作れるLispとphp-srcに手を入れなくちゃいけないPHPだと状況が違いそう。 10年間何も開発しなくてもユーザー側に不満が溜らないかどうかの差はありそう。 ### ユーザーが少ないからニーズが少ないだけなのでは {#ユーザーが少ないからニーズが少ないだけなのでは} それはそう。JavaScriptはユーザーのニーズに答え続けているから複雑になっている。 ユーザー側でニーズが満たせるかどうか、ユーザーのニーズが満たされるか、その変更が自分にとって都合がよいかどうかは人によって違いそう。 ### 塩漬けについてはその環境を再現するということだけで達成できて、塩漬けしたものが現代のランタイムでそのまま動くというのは運がよいだけもしくはランタイムが単に変化してないだけ {#塩漬けについてはその環境を再現するということだけで達成できて-塩漬けしたものが現代のランタイムでそのまま動くというのは運がよいだけもしくはランタイムが単に変化してないだけ} 事前知識のない状態でB'の状態のコードを渡された時に、GolangプロジェクトとPHPプロジェクトだとどちらが動作させやすそうかでいうとGolangと答える人が多いと思っている。 なんとかして動重ないといけない状況で動かしやすそうな言語はある。 Lispプロジェクトが動かしやすいのは昔ながらのプロジェクトが多く、単に依存が少ないだけというのもある。 ### 特にEmacsの場合は破壊的変更を極端に避ける傾向があり、引数が増えるなどワークアラウント的な対応が多くなっている {#特にemacsの場合は破壊的変更を極端に避ける傾向があり-引数が増えるなどワークアラウント的な対応が多くなっている} これはそう。いったんdeprecatedにして数個先のバージョンで削除するみたいな対応もありがち。 破壊的変更を避けると全体の開発速度が落ちるのはそう。 それを良しとするかどうかは文化の問題であり、それを好むかどうかは個人の好み。 ### エコシステムの設計までされているGoやRustでもよいのでLispだからという訳ではないのでは {#エコシステムの設計までされているgoやrustでもよいのでlispだからという訳ではないのでは} 世の中にある大量のプログラミング言語の中での程度の問題だと思っている。 自分の肌感覚だとLispは簡単な部類だと思っている。 ## 個人的な結論 {#個人的な結論} 「Lispが安定しているかどうか」でいうと、これは確実に真だと思っている。 「安定していることはアピールポイントにならない」というのは完全に個人の好みだと思っている。 ユーザーが少ない、用途が限られているが故の安定だとしても意味があるのではないだろうか。 -------------------------------------------------------------------------------- title: "org-crypt運用メモ" date: "2025-01-27" -------------------------------------------------------------------------------- ## Introduction {#introduction} `org-crypt` の使い方をイマイチに理解していなかったが、ふと調べてたら急に理解が進んだ。 個人的な運用が固まったのでメモしておく。 ## Motivation {#motivation} `~/.aws/credentials` のような秘匿情報を `secret.org.gpg` から `org-babel-tangle` から出力していた。 ファイルごとGPGで暗号化すると `git diff` がきれいに取れないので地味に困っていた。 `org-crypt` で必要な分を部分的に暗号化することによって、以前よりはGitフレンドリーにすることが可能になることが分かった。 ## `org-crypt` 使い方 {#org-crypt-使い方} [org-crypt.el](https://github.com/emacs-mirror/emacs/blob/6a390fd42ec4ef97d637899fc93f34ea65639e3c/lisp/org/org-crypt.el) を読むのでもよいが、 [Encrypting the content of Emacs Org files](https://fluca1978.github.io/2021/09/16/Emacs_Org_Encrypt.html) にわかりやすくまとまっている。 次のように設定した。 ```emacs-lisp (with-eval-after-load 'org (setopt org-tags-exclude-from-inheritance '("crypt"))) (with-eval-after-load 'org-crypt (setopt org-crypt-key "0B10DAA7BA0236D7382287660F79C0AB03FD7A1C")) ``` `:crypt:` タグをつけて `M-x org-encrypt-entries` を実行すると次のようにGPGで暗号化される。 タグはヘッダで `C-c C-c` を押すと入力できる。 `M-x org-decrypt-entries` でファイル内全体を復号することも可能。 `:crypt:` タグを変えたいなら `org-crypt-tag-matcher` を設定すればよい。 ```org * before ** access token #+begin_src text P@ssw0rd #+end_src * after :crypt: -----BEGIN PGP MESSAGE----- hQIMA6smtgkkGn+yAQ/+N67k5bFjZ+o/n3cRncdI7AXxZ4fcw7+vM9G/twKBkRqB caleG2+FZy2u3ZHIE04ZnvKKPxZCDdm79Q6QxwBuuLsQiSn/jJ57QwNJySggQjwZ aWQCzBFL5fBHgCR0eTZKX3Otr8Cpe2Eg1oH5SrUMEiRJ+uap+ZeVGs960icDy3AI FRZ7z8BmaqFmA/ZRr3HO2nkFxJutzDnf7uCACa2JxpkJVDrhBOvGVrgHSqn/kzWu +sriWo19z6O2mvVoWXOeg/3PNOP76ZCYFmqAa5zqoutBZ3GVEZ4M+uBcFJtQ6BHT LgWLGNBEJeNjVWF1kGQYot4iArzrSbRzvNsEFzCiGyVYukgJSJuWWlC93aL4bJH+ c52Grie0DDacmiCADUVdUabvx16r82ptRylqV6um7mqdYGmPUErLhkp6zOwwhLgx saAG4lY07DZeEdBtUfnYB39CRqxI/jVwSYoyTQC9PYUyo2y4E7Q2LjcoSyuJ8sPr qgenSMn0gkF86ffm2ITmHDv5iQPgfhdzYIjtwrrDBpwCRkkoDfD68nS/zvekAe74 mO3fUOoM8jmMtQvCo9OMnVKkEXRXnzsyMI12KVIzMakljxnzvgDaELr5s6XTR/0F FgAP19OQNbf24Ax3W+/m5GYxf5ltm+W4qDO0add5z2WLavOYGdknCCLFSN74/VLS bwHums4w2RPZ00nnEX5zI+eN76UM42yZ53gDRjQgfVzfKdX4Rub/D19ZhL+bfqXR iOJwPGz6yfeCDbz4RVvQgd/U8g5LkTrRu/itq9eZpCEjfH/HamC4meOrChI7xvJZ nBkMYucqr7d5DPWEp4gYzQ== =/Ual -----END PGP MESSAGE----- ``` ## `org-babel-tangle` との連携 {#org-babel-tangle-との連携} 自分のユースケースだと `org-babel-tangle` と組み合わせて実行するのだが、 `org-babel-tangle` はencrypt/decryptとは関係のない処理ですので、実行前後でencrypt/decryptする必要がある。 ok: ```org * Secret ** Password #+begin_src text :noweb-ref personal-access-token-github-for-private ghp_xxx #+end_src ** Config #+begin_src yaml :tangle (expand-file-name "~/.config/gh/hosts.yml") :mkdirp yes :noweb yes github.com: user: takeokunn oauth_token: <> git_protocol: ssh #+end_src ``` ng: ```org * Secret ** Password :crypt: -----BEGIN PGP MESSAGE----- hQIMA6smtgkkGn+yAQ/9FbKfh2bZdSPGQo6uEwJMXxYTUPzfE0RwiefZH0DMDwq0 GNPByVgSBd8Cl8U/CyALIwC2FRSkRhnRzbF80ukSaSAacDnmMb4tJFsqSlmG0cll eM5sDGLCehZa06v2x6E6wt6kCE+pawEwTjts917PczOyxmZvA7jPK+uSsLUg5IUw m/ykhS1JuOLs9JuEQdIfZlFp2k1jy3kZMkNsoJV2l25vci6LP4Dc7qsptCZpNU3H zmJRyA7fVPYpCRVdpUzpmsFmYsaEf7LgI0bsCORne+Uy9R+YFakgYj2a0lWAUtVX Z+yKKs0Pmt9EPYP3rC598VPZreNxJJSC/4jlceBwXYJ+13dbebg1xFxoze4fqlzp mTW6yJthHmwGd3O7xMiyoojYqwoQ4wCzeLCT9v4BvGJYbpVKPtQuNUw6aYlvw5Eu IN3/sCq+TTo+KJcrB8H/40XaVgYT9pR69Ak8Ptu+J2txOD6tglQaqMihtJN2+Dzt dvvOONnFd3HOzNL99/ymSTgk4ezypwqjX06TbkdVgWIT5XMKCCI5I/ZAAe3oLW2Z L5F+Gadhkky3dfPuivR6rT/CH8/L/hQzk5ejFkZu9u3ltxC+oeGHWQXsS1CyYqTE fyiUd1u/yME5R+IFlSoG28un48Kc8GoJcydObaKUMDajgPwEZB1TR9rlmSNxCMDS kwEFB49p4TvBosy8762aYp1gwftyg69m5CDtCC5jxpkVuz7GDBphy7qAQlS3gTNN Jb+r9PuJf8h+XfSZqJFyfYGzr0FBITIxI8gu+m+Tf9q7cS93s22w7t2yzS014Dwq 6u4pwk8AG9SxJ4kcPmN7z9kwZwrMlaPImb1boBmKOsnL6onuUO5pNT+PNE3JbSFt NL/rJw== =hxwG -----END PGP MESSAGE----- ** Config #+begin_src yaml :tangle (expand-file-name "~/.config/gh/hosts.yml") :mkdirp yes :noweb yes github.com: user: takeokunn oauth_token: <> git_protocol: ssh #+end_src ``` [org-crypt-use-before-save-magic](https://github.com/emacs-mirror/emacs/blob/6a390fd42ec4ef97d637899fc93f34ea65639e3c/lisp/org/org-crypt.el#L313-L318) は有効にすると、次のような問題が発生する。 1. `org-decrypt-entries` を実行する 2. `org-babel-tangle` を実行すると内部的にsave処理が走る 3. `org-crypt-use-before-save-magic` でsave hookして `org-encrypt-entries` が実行される 4. `org-babel-tangle` 時に空文字で出力される そこでadvice関数で前後にencrypt/decryptする処理を追加した。 ```emacs-lisp (advice-add 'org-babel-tangle :before #'org-decrypt-entries) (advice-add 'org-babel-tangle :after #'org-encrypt-entries) ``` ## 終わりに {#終わりに} 当初の目的を満たせてよかったので満足。 pre-commitでsecretlintを回すようにしているので、encrypt忘れはそちらでもカバーできるようにしている。 余談だが、org-encryptを活用例でいうとこなゆき氏のdotfilesが非常によくできているので、一読することをお勧めする。 ## Backlinks {#backlinks} - [mcp-servers-nixを導入した]({{< relref "20250409210655-introduction_mcp_servers_nix.md" >}}) - [credential管理をorg-encryptからpassword-storeに移行した]({{< relref "20250414001634-migrate_credential_manager_to_password_store.md" >}}) -------------------------------------------------------------------------------- title: "個人的devenv運用" date: "2025-01-27" -------------------------------------------------------------------------------- ## Introduction {#introduction} devenvをヘビーユーズしているので、個人的なdevenv運用についてやTipsについてまとめる。 ## devenvとは {#devenvとは} devenvはcachix社が作っているflake.nixラッパ。 flake.nixよりも圧倒的に高級に書ける割に柔軟性がかなり高いのでプロジェクトに必要なツールを入れる場合に便利。 類似ールにdevboxがありますが、devboxはjsonでのみ記述できるので個人的にはNixで書けるdevenvの方が好み。 ## devevnの個人的な期待役割 {#devevnの個人的な期待役割} 自分のローカル環境でNixが担っている役割は以下。 自分の手元にはNixOSとMacとAndroidのマシンがありますが、環境ごとの役割の差異は特にないです。 1. システム設定 - e.g. network, font, daemon 2. システムグローバルのパッケージ管理 - e.g. fish, Git, Emacs 3. プロジェクト固有のパッケージ管理+α - e.g. php, language-server, pre-commit 4. プロジェクト固有のビルドツール `1. システム設定` と `2. システムグローバルのパッケージ管理` は [takeokunn/nixos-configuration](https://github.com/takeokunn/nixos-configuration) が担っている。 `4. ビルドツール` は [NixでTypstをBuildしGitHub Pagesでホスティングする]({{< relref "20250126114218-build_and_deploy_typst_using_nix.md" >}}) のように `flake.nix` を定義して使っている。 `3. プロジェクト固有のパッケージ管理+α` をdevenvが担っており、 `2. システムグローバルのパッケージ管理` は必要最小限に抑えているので都度インストールする必要がある。 自分は普通の人よりも幅広い言語やフレームワークのリポジトリを扱うので、逐次必要なツールを明示的に入れている。 自分の為に用意しているので、プロジェクトには `git push` せず、自分用プライベートリポジトリで管理している。 また Emacsからもよい感じにpackageを実行できるようにする必要があるので工夫が必要。 ## 実運用 {#実運用} ### Laravelプロジェク用devenvのサンプルコード {#laravelプロジェク用devenvのサンプルコード} 実際に会社のLaravelプロジェクトで使っているコードは以下。 - language serverやawscliなどプロジェクで必要なものは都度入れている - 言語のバージョンを大まかに定義する - phpなら `8.2` という定義をして、 `8.2.x` レベルの細かい指定まではしない - php.iniはdevenv内で定義する - pre-commitをストレスのない範囲で可能な限り設定する - ref. ```nix { pkgs, config, inputs, ... }: { cachix.enable = false; dotenv.disableHint = true; env.COMPOSER_MEMORY_LIMIT = "4G"; packages = with pkgs; [ gh hub nodePackages_latest.intelephense ssm-session-manager-plugin tbls rain mariadb awscli ]; languages.javascript = { enable = true; package = pkgs.nodejs_20; yarn.enable = true; }; languages.php = { enable = true; package = pkgs.php82.buildEnv { extensions = { all, enabled }: with all; enabled ++ [ xdebug ]; extraConfig = '' memory_limit=-1 ''; }; }; pre-commit.hooks = { actionlint.enable = true; editorconfig-checker.enable = true; check-json.enable = true; check-merge-conflicts.enable = true; check-yaml.enable = true; check-case-conflicts.enable = true; }; } ``` ### org-tangleして各プロジェクトに配置 {#org-tangleして各プロジェクトに配置} [emacs-mirror/emacs](https://github.com/emacs-mirror/emacs) の場合は次のようなOrgファイルを定義して `org-babel-tangle` する。 `devenv.lock` は管理運用方法は現状思いついてないので諦めている。 ```org *** emacs **** .git/info/exclude #+begin_src fundamental :mkdirp yes :noweb yes :tangle (if (file-directory-p "~/.ghq/github.com/emacs-mirror/emacs/") (expand-file-name "~/.ghq/github.com/emacs-mirror/emacs/.git/info/exclude") "no") .envrc devenv.nix devenv.lock .devenv.flake.nix .devenv/ .direnv/ #+end_src **** .envrc #+begin_src dotenv :noweb yes :tangle (if (file-directory-p "~/.ghq/github.com/emacs-mirror/emacs") (expand-file-name "~/.ghq/github.com/emacs-mirror/emacs/.envrc") "no") source_url "https://raw.githubusercontent.com/cachix/devenv/95f329d49a8a5289d31e0982652f7058a189bfca/direnvrc" "sha256-d+8cBpDfDBj41inrADaJt+bDWhOktwslgoP5YiGJ1v0=" use devenv #+end_src **** devenv.nix #+begin_src nix :noweb yes :tangle (if (file-directory-p "~/.ghq/github.com/emacs-mirror/emacs") (expand-file-name "~/.ghq/github.com/emacs-mirror/emacs/devenv.nix") "no") { pkgs, config, inputs, ... }: { cachix.enable = false; dotenv.disableHint = true; packages = with pkgs; [ autoconf texinfo gnutls libgccjit zlib libxml2 ncurses ]; } #+end_src ``` ### direnvで起動 {#direnvで起動} project rootに発行したらdevenv shellに入るようにdirenvを設定している。 ### .dir-locals2.elでPATHを通す {#dot-dir-locals2-dot-elでpathを通す} 次のような `.dir-locals2.el` を `org-babel-tangle` で出力してEmacsにパスを通している。 ```emacs-lisp ((nil . ((eval . (add-to-list 'exec-path "~/ghq/github.com/org-name/project-name/.devenv/profile/bin/")))) (php-mode . ((eval lsp)))) ``` ## 所感 {#所感} devenvめちゃくちゃよい。 ServicesやTestsなどの機能も試していきたい。 ## Backlinks {#backlinks} - [巨大なLaravelレポジトリでphpactorを使うTips]({{< relref "20250515004832-phpactor_performance_huge_projects.md" >}}) -------------------------------------------------------------------------------- title: "NixでTypstをBuildしGitHub Pagesでホスティングする" date: "2025-01-26" -------------------------------------------------------------------------------- ## 背景 {#背景} スライドや組版記事を生成する為にTypstを採用することにした。 工数を最小限に安定的に量産できるようなしくみを整える必要が迫られていたので、今回対応したことをメモしておく。 ソースコードは[takeokunn/blogのtypstディレクトリ下](https://github.com/takeokunn/blog/tree/11609d8503ffd9b9eb8a3e2a5d19f3f51ee5b579/typst)に置いている。 ## 条件 {#条件} - MUST - Localに一切依存しない再現性の高いBuild環境構築 - org-modeで記述できる - Typstのエコシステムを使う - スライド生成と組版記事両方に対応する - SHOULD - takeokunn.orgから配信できる - リリースまでのリードタイムが短い - org-roamなど周辺のエコシステムとの親和性を高める ## 方針 {#方針} 次の3ステップで実現した。 - org-modeで記述したファイルをTypstに変換 - NixでTypstをBuildできるようにする - GitHub ActionsでBuildしてGitHub Pagesで配信できる {{< figure src="/images/D91F417B-0FFC-4C67-A814-A267565ADE63.png" >}} ## 作業ステップ {#作業ステップ} ### 0. どこに置くか検討 {#0-dot-どこに置くか検討} 以下を理由に [takeokunn/blog](https://github.com/takeokunn/blog) 上に構築することにした。 - [takeokunn.org](https://www.takeokunn.org/) から配信できる - 既存のorg-roam上に相乗りできる - 新しいリポジトリを作ると関心ことが増える - すでにあるGitHub Actions上に構築することによって実装コストとメンテナンスコストを抑えられる - 今回の用途だと作業途中でもパブリックにして問題がない 商業誌のような公開制限をかける必要のあるものは都度プライベートリポジトリを作って対応することにした。 ### 1. org-modeで記述したファイルをTypstに変換 {#1-dot-org-modeで記述したファイルをtypstに変換} 組版記事の場合、[jmpunkt/ox-typst](https://github.com/jmpunkt/ox-typst) を用いて通常とおりOrgファイルをTypstに変換する。(`M-x org-typst-export-to-typst` を実行) [PHPerKaigi2025のパンフ記事](https://github.com/takeokunn/blog/blob/11609d8503ffd9b9eb8a3e2a5d19f3f51ee5b579/typst/phperkaigi-2025-pamphlet/article.org)の場合、次のようにTypstの設定をExportしている。 ```org #+BEGIN_EXPORT typst #set text(lang: "ja", font: "Migu", size: 8pt) #set page( width: 210mm, height: 297mm, margin: 20mm, columns: 1 ) #import "@preview/codly:1.2.0": * #import "@preview/codly-languages:0.1.1": * #show: codly-init.with() #codly(languages: codly-languages) #align(center)[ #set text(size: 18pt) Phpactorから学ぶLanguage Server Protocolの仕組み #set text(size: 12pt) たけてぃ \@takeokunn ] #+END_EXPORT ``` スライドの場合、 Orgファイルには登壇メモをしつつ、 `#+BEGIN_EXPORT typst` のみ出力してほしかったので次のようなElispを書いた。 ```emacs-lisp (require 'ox-typst) (setq org-export-with-toc nil) (org-export-define-backend 'typst-slide '((export-block . org-typst-export-block) (headline . org-typst-headline) (item . org-typst-item) (keyword . org-typst-keyword) (section . org-typst-section) (src-block . org-typst-src-block)) :menu-entry '(?y "Export to Typst" ((?F "As Typst buffer" org-typst-export-as-typst) (?f "As Typst file" org-typst-export-to-typst) (?p "As PDF file" org-typst-export-to-pdf))) :options-alist '((:typst-format-drawer-function nil nil #'(lambda (_ contents) contents)) (:typst-format-inlinetask-function nil nil #'(lambda (_ contents) contents)))) (defun org-typst-slide-export-as-typst (&optional async subtreep visible-only body-only ext-plist) (interactive) (org-export-to-buffer 'typst-slide "*Org Typst Slide Export*" async subtreep visible-only body-only ext-plist)) (defun org-typst-slide-export-to-typst (&optional async subtreep visible-only body-only ext-plist) (interactive) (let ((outfile (org-export-output-file-name ".typ" subtreep))) (org-export-to-file 'typst-slide outfile async subtreep visible-only body-only ext-plist))) ``` ### 2. NixでTypstをBuildできるようにする {#2-dot-nixでtypstをbuildできるようにする} 組版記事とスライドの場合で実行したいElisp関数が違うので、引数に `type` を渡すことで条件分岐をした。 Nix経由でインストールしたものを `TYPST_FONT_PATHS` `TYPST_PACKAGE_PATH` でPATHを通して `typst compile` を実行するDerivationを作った。 output抜粋: ```nix buildTypstProject = { name, type }: let _ = assert builtins.elem; type [ "article" "slide" ]; emacsBuildPhase = name: if type == "article" then "emacs --batch --load ox-typst.el --file ${name}/article.org --funcall org-typst-export-to-typst" else "emacs --batch --load ox-typst.el --file ${name}/article.org --funcall org-typst-slide-export-to-typst"; in pkgs.stdenv.mkDerivation { inherit name; src = ./.; nativeBuildInputs = with pkgs; [ typst migu (emacs.pkgs.withPackages (epkgs: with epkgs; [ org ox-typst ])) ]; buildPhase = '' ${emacsBuildPhase name} export TYPST_FONT_PATHS="${pkgs.migu}/share/fonts/truetype/migu" export TYPST_PACKAGE_PATH="${typstPackagesCache}/typst/packages" typst compile ${name}/article.typ ''; installPhase = '' mkdir -p $out cp ${name}/article.pdf $out/${name}.pdf ''; }; ``` 呼び出し方はシンプルで、次のように `packages.*` で定義するとBuildできるようになった。 ```nix packages = { example-slide = buildTypstProject { name = "example-slide"; type = "slide"; }; phperkaigi-2025-pamphlet = buildTypstProject { name = "phperkaigi-2025-pamphlet"; type = "article"; }; }; ``` `#import "@preview/codly:1.2.0": *` のようにインポート記述のみすると、Nix Sandbox環境だとうまくインストールできなかった。(参考: [Typixを使って複数環境でtypstでスライドをコンパイルする - Zenn](https://zenn.dev/omochice/articles/reproducible-compilation-of-typst-by-typix)) inputsに [typst-packages](https://github.com/typst/packages) を定義してPATHを通すとうまくBuildできた。 TypstのNixラッパである [loqusion/typix](https://github.com/loqusion/typix) のコードも読んだが、自分の用途だと自前で書けばよいという結論に至ったので採用しなかった。 inputs抜粋: ```nix inputs = { typst-packages = { url = "github:typst/packages"; flake = false; }; }; ``` ### 3. GitHub ActionsでBuildしてGitHub Pagesで配信できる {#3-dot-github-actionsでbuildしてgithub-pagesで配信できる} Hugoのデプロイフローの最後に `nix build` して生成したPDFを `public/pdf/` にコピーする処理を追加した。 ```yaml - name: Generate example-slide run: | nix build ./typst#example-slide cp result/example-slide.pdf public/pdf/ - name: Generate phperkaigi-2025-pamphlet run: | nix build ./typst#phperkaigi-2025-pamphlet cp result/phperkaigi-2025-pamphlet.pdf public/pdf/ ``` 生成されたPDFは以下。 - - ## Next Step {#next-step} 安定的にBuildできるようになったので、Typst自体の記述に慣れつつスライドや記事を量産していきたい。 また、現状[Miguフォント](https://itouhiro.github.io/mixfont-mplus-ipa/migu/)を使っているが個人的には納得していなく、テーブル表示にするとなぜかずれてしまう問題が発生している。 nixpkgs内にある日本語フォント選定に時間を割きたい。 ## 雑感 {#雑感} 当初掲げていた条件をすべて満たせたので満足。 [Typixを使って複数環境でtypstでスライドをコンパイルする - Zenn](https://zenn.dev/omochice/articles/reproducible-compilation-of-typst-by-typix) 記事に助けられたのでOmochiceに大感謝。 ## Backlinks {#backlinks} - [技術イベントのパンフレット記事をTypstでBuildする]({{< relref "20251130140627-build_pamphlet_article_by_typst.md" >}}) - [2025年2月名古屋旅行]({{< relref "../diary/20250224140741-retrospective_nagoya_2025.md" >}}) - [Typst自作テーマで登壇スライドを作成した]({{< relref "20250601214129-create_slide_by_typst_self_theme.md" >}}) - [AIを駆使してTypstスライドを生成する]({{< relref "20250610215948-ai_powered_typst_slide_generation.md" >}}) - [個人的devenv運用]({{< relref "20250126135045-personal_devenv_tips.md" >}}) - [Typstとpdfpcでプレゼン機能を実現する]({{< relref "20250608145128-presenting_with_typst_pdfpc.md" >}}) - [Typstテーマを自作した]({{< relref "20250506220914-create_typst_slide_theme.md" >}}) -------------------------------------------------------------------------------- title: "2024年 全体振り返り" date: "2025-01-05" -------------------------------------------------------------------------------- ## Introduction {#introduction} 2024年振り返りの振り返り記事です。 立てた目標に対してどうだったか、2025年はどうするべきかを書いておきます。 ## 年始に立てた目標 {#年始に立てた目標} プログラミングのほぼすべての問題は「Aという問題に対してBというツールを使ってCをする」の連続だということに気がつきました。 「日報を書く時はorg-modeのテンプレートを使って記述する」や「日本語を書く時はskkを使って入力する」など、それぞれの問題は細分化をするとほぼすべてこの形式になります。 プログラマーは得てしてAという問題を解決したいだけなのに、「BよりB'の方がよいのではないか」と検証をし始めていっこうにCできないものです。 とはいえツールの選定は生産性に直結するので重要でありそこの塩梅が非常に難しいです。 そこで2024年は5年後も使えるであろうツールの選定を十分にすることにしました。 自分が持っているすべての「Aという問題に対してBというツールを使ってCをする」をテンプレ化し、再利用性と再現性を高くするのを目標にしました。 いろいろな話を見聞きした結果、次の3つを頑張るのが良さそうだということで目標に据えました。 - 文芸的プログラミング(org-mode) - Nix - LLM/Local LLM ## 目標に対しての振り返り {#目標に対しての振り返り} | 目標 | 自己評価 | |-------------|------| | 文芸的プログラミング | ○ | | Nix | ◎ | | LLM/Local LLM | △ | 2024年はNix元年というくらい、Nixに大量に時間を投資したのは大成功でした。 自分が持っているほぼすべての設定を [takeokunn/nixos-configuration](https://github.com/takeokunn/nixos-configuration) とprivate repoに移植しました。 明示的にコードに落とすことによって、自分は今何をどういう設定でどういう用途で使っているのかを明確にできました。 文芸的プログラミングは去年書いた [2023年のorg-mode活用と今後の抱負]({{< relref "20240112071806-my_relationship_with_org_mode_in_2023_and_how_navigate_in_2024.md" >}}) から大きな進展はないですが、細やかな改善をたくさん行えた結果、ほぼテンプレ行動でやりたいことを実現できるようになりました。 LLM/Local LLMに関しては、どのくらいの将来性があるのか、やりこむとしたら何をやればよいのか、Local LLMなら何ができるのか等を自分の中で整理するというのが目標でした。 一応自分の中では整理がついたのでそれは良かったのですが、Difyの活用やワークフローに落とし込む所までは間に合わなかったです。 ## コミュニティ活動 {#コミュニティ活動} | 時期 | イベント名 | 役割 | 登壇スライド | 関連 | |----|------------------------------------------------------------------------|-------|------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| | 1月 | [PHPカンファレンス北海道](https://phpcon.hokkaido.jp/) | 参加者 | | [PHPカンファレンス北海道2024に参加してきました]({{< relref "../diary/20240112063123-retrospective_phpcondo_2024.md" >}}) | | 3月 | [phperkaigi 2024](https://phperkaigi.jp/2024/) | パンフ記事執筆 | [テキストエディタがPHPをシンタックスハイライトする仕組みとモダンテキストエディタ事情について]({{< relref "20240107122028-how_to_syntax_highlight_in_text_editor.md" >}}) | | | 4月 | [PHPカンファレンス小田原](https://phpcon-odawara.jp/2024/) | 登壇 | [Laravelに0からPHPStanを導入して継続的に運用する方法](https://docs.google.com/presentation/d/1436T1dx_0igdNzNCVpGIf0kVA5wOVF4kcVmH9VRJobA/edit#slide=id.p) | | | 6月 | [PHPカンファレンス福岡](https://phpcon.fukuoka.jp/2024/) | 登壇 | [Nixを用いて再現可能なPHP環境を用意する](https://docs.google.com/presentation/d/1I5yWPV_hIOaN0FfRQPwyhdQTVFv2uQEK0A08sMHPLiA/edit#slide=id.p) | | | 8月 | [東京Emacs勉強会 サマーフェスティバル2024](https://tokyo-emacs.connpass.com/event/321255/) | 主催 | | | | 10月 | [Nix meetup #1](https://nix-ja.connpass.com/event/330557/) | 主催、登壇 | [nix-shell+org-modeで文芸的プログラミング](https://docs.google.com/presentation/d/1So6l6JTNlZkrQDxf99kMFIlPB34TuNdQjpNwgxIBSFo/edit#slide=id.p) | | | 11月 | [技術書展17](https://techbookfest.org/event/tbf17) | 売り子 | | [めだかのclojure.core学校 conao3](https://techbookfest.org/product/f7rPGt7NeeDuqxYdE3Teyc?productVariantID=hATFZbCg6sJxMt5dCXPn4x) | | 11月 | [VimConf 2024](https://vimconf-2024-ticket.peatix.com/) | 個人スポンサー | | [VimConf2024 参加記]({{< relref "../diary/20241123101757-participation_vimconf_2024.md" >}}) | | 12月 | [ISUCON14](https://isucon.net/archives/58869617.html) | 競技者 | | [ISUCON14にチームOL001として参加しました(インフラ編)](https://zenn.dev/takeokunn/articles/20241217090756) | | 12月 | [PHPカンファレンス東京](https://phpcon.php.gr.jp/2024/) | 企業スポンサー | | | 今年は毎月のように何かがあった1年でした。 10月に主催した [東京Emacs勉強会 オクトーバーフェスティバル2024](https://tokyo-emacs.connpass.com/event/330572/) と [Nix meetup #1](https://nix-ja.connpass.com/event/330557/) は大盛況でオリジナルカクテルを作ったりしました。 来年以降のphpcon関連のすでに名古屋、phperkaigi、小田原が決まっています。 また、vim-jpラジオ出演や商業誌の雑誌寄稿が決まっていて来年も忙しくなりそうです。 ## イベント {#イベント} 2024年はvim-jpに入り浸ってた一年でした。 emacserのtomoyaさんやconao3と仲良くなれたし、yasunori projectと称してyasunoriの転職活動を全力サポートしました。 カンファレンスついでに旅行たくさんいきました。 | 時期 | 場所 | 目的 | |----|-----|----------| | 1月 | 北海道 | PHPCon北海道 | | 3月 | 秋田 | どっかにビューン旅行 | | 4月 | 小田原 | PHPCon小田原 | | 4月 | 大阪、神戸 | 従兄弟の結婚式 | | 6月 | 福岡、別府 | PHPCon福岡 | | 8月 | 横須賀 | 地元民の友達と遊ぶ | ぷぷさんが主催している [エンジニアティールーム](https://eng-tearoom.connpass.com/) はほぼ皆勤賞なくらい参加しました。 ## 仕事について {#仕事について} ### 本業 {#本業} 去年は [PHPConf2023「保守性の高いアプリケーションを作り続けるための基盤整備について」というテーマで登壇します](https://zenn.dev/openlogi/articles/bba928c9e07af3) と書いたようにいろいろやりましたが、正直そんなに頑張れてないなという反省があります。 5段階中3くらいの評価でした。 チーム内の人間関係は成功したなーという実感があり、その部分は良かったです。 職場の9月くらいまで雰囲気が悪かったが今は雰囲気が回復しました。 やはり退職時に暴れ回るのはよくないんだなということを実感しました。 業務面では自分専用のRunbookをとにかく作り、どんどん快適になっていく実感があります。 物流ECドメインに詳しくなって食いっぱぐれないようにしたいですね。 ### バイト {#バイト} 土祝日は必ず行くホームになっています。 自分としては好きな技術を使んでるだけなのですが、スタートアップに半身でも置くことはやはり重要なんだなと定期的に思います。 Google App ScriptやWordPressの可能性に気が付けたのは大きかったです。 ## 来年の目標 {#来年の目標} 次の3つをやっていきます。 - 車輪の再発明 - ソフトウェアテスト - 外部へのリーチを増やす 「車輪の再発明」に関して、コンパイラの自作や以前から作りたかったものを作ります。 アプリケーション実装力を高めるのが目的で、特にLispとPrologに対して時間を投資をしてく予定です。 「ソフトウェアテスト」に関して、生成AIが2024年に一気に伸びて日々のコーディングでも採用できるようになりました。 そうなると動作保証する方法が重要になってきます。 投資していくツールの選定をし、ひたすら使い込み、継続的にテストを回す方法を模索したいです。 「外部へのリーチを増やす」に関して、具体的に次のようなことをやります。 - ブログやスライドの運用を洗練させる - EmacsConf 2025開催する - 勉強会や登壇頻度を増やす - `技術書展` で本を出したり、雑誌への寄稿をする ## 終わりに {#終わりに} 何かを深掘して時間を溶かして調べてよいかの指標として年始に大まかな目標を立て取り組むのは重要だなと感じました。 自分としてはアプリケーションや外部活動に十分に集中するだけの基盤が整ったので、来年はアウトプットに全力を注ぐ1年にしたいです。 -------------------------------------------------------------------------------- title: "VimConf2024 参加記" date: "2024-11-23" -------------------------------------------------------------------------------- ## はじめに {#はじめに} 2024-11-23 Satに行われたvimconf 2024に参加しました。 ## 登壇感想 {#登壇感想} 英語の登壇であること、前日はしゃいで睡眠不足あったので聞き逃した所もあるが登壇メモを書いておく。 ### Keynote - The new Vim project - What has changed after Bram by Christian Brabandt {#keynote-the-new-vim-project-what-has-changed-after-bram-by-christian-brabandt} #### 一言 {#一言} vim projectを引き継ぐにあたって大変なことについて。 vim本体のコードの引き継ぎも大変だけど、それ以外の引き継ぎも大変だったという話。 #### 内容メモ {#内容メモ} - Bramが超人すぎて引き継ぎ量が多い - git repoの引き継ぎ - 権限周りや新しいメンテナの招待 - vim.org のホームページ引き継ぎ - php5/7とか古いバージョン - DBやらドメインやら - Emailアカウント周り - Google Groupを使ってる - 正直イマイチなのでどうしたものか - セキュリティレポート - [Huntr](https://huntr.com/bounties/63f51299-008a-4112-b85b-1e904aadd4ba) - GitHub security advisory - private mailing list - [ICCF](https://iccf-holland.org/) - まだ動いてるし、変えずにやっていく - [voting feature](https://www.vim.org/sponsor/vote_results.php) は廃止 - 開発進捗 - Development did not stop - Vim9 classサポート - XDGサポート/Editorconfigサポート/Waylandサポート - BC Breakが起きないように十分にテストをする - コミュニティにとって何が重要か - e.g. IDEっぽいことができた方が良いか、エディタのままでいいか - e.g. Vim9は楽しいかどうか等 - 開発者に求めてること - 安全なC関数を使う - CIを通してカバレッジの確認をするなど - 管理しやすいようにリファクタリングをする - 何を改善すべきか - GUI(GTK4)などのサポートを向上させるかどうか - Tree-sitterサポートするかどうか - 結論 - Vimメンテナンスは大変 - コミュニティを健全性を維持したい - 質疑応答 - NeovimとVimは相互に協力して開発を進めていきたい - Vimにコントリビューションを始める方法を教えてほしい - ドキュメントなど全ての貢献に感謝してる - Vim scriptのC言語を読めば良い - 貴方が独裁者になってない? - 自分が休暇中は他の人がmerge作業をしてる - コミュニティの意向を尊重してmergeしてる - 言語の壁でも越えるのが大変だけどどうしてる? - 英語でコミュニケーションしかできない - メンテナは各国にいるので英語がコミュニケーションの言語になってる - 初心者に対してどういうことを期待してる? - まず日本のコミュニティに繋がることから始めると良い - 他の人から学ぶ、バグレポートから始めたり、ソースコードをチェックしてどう変更されたのか確認する、テストをする - ソースコードに馴染んできた時にパッチを送ると良い ### Keynote - (Neo)Vim Made Me a Better Software Developer by TJ DeVries {#keynote--neo--vim-made-me-a-better-software-developer-by-tj-devries} #### 一言 {#一言} Neovimの話というよりは仕事のやり方に通じるものだった。 youtuberっぽい警戒なトークで面白すぎた。 #### 内容メモ {#内容メモ} - 自己紹介 - neovimとtelescopeのcontributor - より良いソフトウェア開発者の定義 - 指標がむずかしい - お金とかでは測れない - Leetcodeがうまいとかではない - 一緒に開発してる人が喜んでくれるかどうか、作ったものを楽しんで使ってくれるかどうか - Goal - vimとinteractionをとる方法 - vimがなんでワクワクするか、他の人にどう伝えるか - No, Please No. - It's written in C, so it must be fast! - HA! YOU USE A BROWSER TO EDIT TEXT!! - Only noobs use VS C\*de - It looks cool in the coffee shop/interviews - Yes, But No. - Better than "C IS FAST!!" - テキスト編集 - neovimの方が早いだとかどういうことは言わない - Plugins - 問題解決にならなければ意味がない - Yes, But (Just Barely) No - OSSは優れているが、全てを再現するのが難しい - Joy - プログラミングはキャリアの中でやってる - マラソンに参加してるようなもの - Neovimを使って色々なことを改善できる - 1%の改善でも長くやってれば大きな差になる - Personalized Development Enviroment(PDE) - Yes - Epicが最初のキャリア - Fortniteではなく、医療系システム - Life always has tradeodff - やる気がとても大事なので自分ごとにして取り組む - 自分のためのものを作る、なにがほしいのかがわかるというのは素晴らしいことである - 自分が使って「気持ち悪い」という感覚をもつ - 本番環境 = 自分のマシン - 練習が大事 - Prictice - ビジョンを明確にする - デバッグをうまくやる - シンプルさを保つ - 他の人と強調する - コミュニケーションする - iterationスピードを上げる - Confession - playgroundを作る - Neovimのだけじゃなくてとにかく試す機会を増やす - Make it \* vim ### Mastering Quickfix by daisuzu {#mastering-quickfix-by-daisuzu} #### 一言 {#一言} QuickfixはEmacsで言うところのembarkのような機能らしい。 #### 内容メモ {#内容メモ} - Quickfixについて解説 - Basic Operation - List Creation - Managing quickfix window - Jumping - History Listについて - Customizing Quickfixについて - grep programを指定できる - Batch Operation - `:cdo` , `:cfdo` がある - Saving and loading quickfix lsits - Automating with macros - Real-World Example - 「エディタの達人」の時便利らしい - THe Essence of Vim Mastery - Vimの語彙を増やしてMasterしよう ### Hacking Vim script by kato {#hacking-vim-script-by-kato} #### 一言 {#一言} Vimに関数を生やす話。php-srcと似てたので理解しやすかった。 #### 内容メモ {#内容メモ} - Vimのソースコードを手元に落とす - ctagsでタグを作る - これで定義ジャンプできるようになる - 関数実装 - C言語で関数を実装する - 関数の定義をglobal_functionsに定義する - 辞書順にsortされた所に入れる - 入力補完には出てくるのに実行時に解決できないことがある - `f_debug()` を実装してみる - src/evalfunc.c - `f_max_argc` や `f_argccheck` などの説明など - デバッガ - gdbを使えるし、termdebugがある - `git blame` 便利 - 実行までの流れ - ASTを作って式の評価など - GCの話 - Vim9 Scriptについて - stack machineにコンパイルされる - stack machine VM説明 ### Switch between projects like a Ninja by Yuki Ito {#switch-between-projects-like-a-ninja-by-yuki-ito} #### 資料 {#資料} #### 一言 {#一言} 私はEmacsだと [projectile](https://github.com/bbatsov/projectile) を使ってる #### 内容メモ {#内容メモ} - プロジェクトの切り替えについて - インフラプロジェクトとアプリケーションプロジェクトの切り替えをどうする? - sessionの管理どうしよう - Vim SessionについてDeepDive - vimにも `mksession` がある - sessionの実装について - Advancedな使い方について - shada ### Vim meets Local LLM: Edit Text beyond the Speed of Thought by yuys13 {#vim-meets-local-llm-edit-text-beyond-the-speed-of-thought-by-yuys13} #### 一言 {#一言} ellamaを使ってる身としては興味深かった。Emacsの小ネタも入れてくれて嬉しい。 #### 内容メモ {#内容メモ} - LLMでコードを生成する話 - 何故Local LLMなのか - 仕事でAIにコードを送るのがダメになりがち - Local LLMだと利用用途が広がる - Ollama導入 - `$ ollama pull tinyllama` - curlで叩く実例 - 仕組み解説 - FIM(Fill in the Middle) - FIMのAPI経由で検証 - Vimとの繋ぎ込み - Plugin実装の話 - 将来性 - プロジェクト内のコードを使って生成する - UI体験を考える ### Creating the Vim Version of VSCode Dev Container Extension: Why and How by mikoto2000 {#creating-the-vim-version-of-vscode-dev-container-extension-why-and-how-by-mikoto2000} #### 一言 {#一言} devContainer使ったことがなかったのでかなりイメージがついて良かった。 #### 内容メモ {#内容メモ} - VSCodeのdevContainerについて - VSCode Server解説 - devcontainers/cli - devcontainer.vim解説 - 弱点 - yank問題など - clipboard data receiverを作ってTCPでやりとりする - まとめ - vim integrationツールを作れる ### Neovim for Frontend Developers: Boosting Productivity and Creativity by ryoppippi {#neovim-for-frontend-developers-boosting-productivity-and-creativity-by-ryoppippi} #### 一言 {#一言} トークがうますぎる。お前がナンバーワンや!!! #### 内容メモ {#内容メモ} - エディタ遍歴 - VSCodeのVim Pluginから始めてNeovim - VSCodeについて - 世界一人気だけど設定できない - Neovimの良い所 - hackable - モダンwebフロントエンドのNeovim - 色々な言語やツールがあって大変 - ライブコーディング - vim pluginの紹介など - コーディング - テストインテグレーション - ファイル管理 - 結論 - Neovim使おう ### Building Neovim Plugins: A Journey from Novice to Pro by 2KAbhishek {#building-neovim-plugins-a-journey-from-novice-to-pro-by-2kabhishek} #### 一言 {#一言} `Tips for Plugin Authors` すごい良い話をしてた。 #### 内容メモ {#内容メモ} - Neovim遍歴 - 色々pluginを作ってきた話 - 何故Pluginを開発するのか - Boost Productivity - Make Your Own Tools - Enhance Integration - Empower the Community - Plugin開発周りの話 - helpやcommentをちゃんと書く - Advanced Plugin - Async API - Integration tools(e.g. docker) - robust UI - tree-sitter/lsp - Tips for Plugin Authors - 自分が0番目のユーザ、ドキュメント化する、自動テストをする、パフォーマンスに気をつける、ユーザ設定を尊重する、シェアしてケアする ### Can't Help Falling in Vim ~ Wise men say only fools reinvent the wheel, but I can't help building yet another fuzzy finder: Fall by Λlisue {#can-t-help-falling-in-vim-wise-men-say-only-fools-reinvent-the-wheel-but-i-can-t-help-building-yet-another-fuzzy-finder-fall-by-λlisue} #### 一言 {#一言} またFuzzy Finderか!!! #### 内容メモ {#内容メモ} - Fuzzy Finderとは - FuzzyにFindするもの - Fuzzy Finder/Tree Viewerの違い - Fuzzy finder - fast filering - partial knowlegdge - tree viewer - quick - ... - Fuzzy finder歴史 - 2007年 FuzzyFinder star: 257 - 2010年 unite.vim star: 2900 - 2011年 ctrlp.vim star: 2011 - 2015年: fzf.vim star: 9600 - 2016年: denite.nvim start: 2100 - 2018年: fzf-preview.vim star: 909 - 2020年: telescope.nvim star: 15500 - 2021年: ddu.vim star: 292 - 2024年: Fall star: 30 - Fall Main Feature - Vim/Neovimサポート - 本当に必要なものだけに注力 - プログラマフレンドリー - Vim/Neovimサポート - 両方サポートしてるPlugin少ない - Floating UIサポート - denops使ってる - 本当に必要なものだけに注力 - modeless UI - 「忘れるべくして生まれてきた」 - Switch behaviors - シームレスに切り替えることができる - submatch to refine - Latency over throughput - プログラマフレンドリー - TypeScriptで記述できる - extension開発ができる - TS moduleとして配布できる - 結論 - Fall is venry ### The latest dark deno powered plugins by Shougo Matsushita {#the-latest-dark-deno-powered-plugins-by-shougo-matsushita} #### 一言 {#一言} テキストエディタ、つまりこの世の全てが話されていた。 #### 内容メモ {#内容メモ} 「VimはOS/シェル/ターミナルである」「VimはEmacs(のようなアプリケーションプラットフォーム)である」「Vimは全てである」 ## LT感想 {#lt感想} 疲れてきてメモ量が少なくなってしまった。 ### .vimrc and my belief about it by omochice {#dot-vimrc-and-my-belief-about-it-by-omochice} - vimは趣味から仕事のツールに変化した - vimと外部APIとの繋ぎ込みを考えることが増えた - 他の人のvimrcを読む機会が増えた - 変わらなかったこと - 自分の思考を妨げない、最新のものを使う、明示的に書く ### Plugin dependecny management wih nix by kuuote {#plugin-dependecny-management-wih-nix-by-kuuote} - 外部依存を管理するならNix - Nix is All You Need - ### Develop iOS apps with Neovim by uhooi {#develop-ios-apps-with-neovim-by-uhooi} - DAPの説明 - neovim pluginは以下をメインで使ってる - xcodebuild.nvim - nvim-dap, nvim-dap-ui ### Future-Proof Your Vim plugins: Strategies for Robust Testing {#future-proof-your-vim-plugins-strategies-for-robust-testing} - テスト書いてると色々支障が出る、vimでも同じ - テストフレームワーク選定 - shellから叩けるか - 環境依存を減らしたい - 依存プラグインも入れられる - [thinca/vim-themis](https://github.com/thinca/vim-themis) を使うと良い ### Tips for natvigating and Exploring Vim's Help by Satoru Kitaguchi {#tips-for-natvigating-and-exploring-vim-s-help-by-satoru-kitaguchi} - vim helpの話 - 色々なhelpの引き方がある ## 交流など {#交流など} 前日は秋葉原周辺でvimmer達と8人一緒に泊まり、朝一で向かいました。 昼飯は今半のすき焼弁当で余ってたので2つ食べました。 また、tadsanとvim-jpラジオの写真を取りました。 懇親会ではvim-jpの人達と交流できたので大満足でした。 特にShougoさんとテキストエディタの思想について交流できたのは個人的には大きかったです。 タイル型VMを使う理由、OSに対しての感覚、AstroNvimのようなフルスクラッチなnvimについての感覚などかねてから聞きたかったことが聞けました。 また、Shougoさんにはかねてからライブコーディングをしてもらいたいとお願いしてた夢が叶いました。 また、Emacsの話をいっぱい出来ました。 ゴリラvimと東京Emacs勉強会コラボとNix Meetupコラボしましょうという話もしました。 ## 終わりに {#終わりに} 本当に充実したVimConfだった。最高だった。 ## Backlinks {#backlinks} - [2024年 全体振り返り]({{< relref "../permanent/20241221130328-retrospective_2024.md" >}}) -------------------------------------------------------------------------------- title: "tcardgenをnixでパッケージ化する方法" date: "2024-11-22" -------------------------------------------------------------------------------- ## 背景 {#背景} HugoからOGPを動的に吐き出すツールとして [Ladicle/tcardgen](https://github.com/Ladicle/tcardgen) がある。 2024/11/22 現在当ブログでも利用していて、 [takeokunn/blog](https://github.com/takeokunn/blog) のNix化に伴い tcardgen もNixでパッケージングする必要があった。 ## 流れ {#流れ} ### 1. 自作nur-packages に登録 {#1-dot-自作nur-packages-に登録} [takeokunn/nur-packages](https://github.com/takeokunn/nur-packages) に雑に次のようにパッケージングをした。 ```nix { buildGoModule, fetchFromGitHub }: buildGoModule { pname = "tcardgen"; version = "0.0.1"; src = fetchFromGitHub { owner = "Ladicle"; repo = "tcardgen"; rev = "2222547ac37c2d6e1961b00acef3771f48ac8220"; hash = "sha256-6Z4SWpjdPMMCC6xm+xjSNAWQpO2FD91p+Mk9Y+Hh7AY="; }; vendorHash = ""; doCheck = false; meta = { description = "Generate a TwitterCard(OGP) image for your Hugo posts."; homepage = "https://github.com/Ladicle/tcardgen"; mainProgram = "tcardgen"; }; } ``` `src` 部分は [seppeljordan/nix-prefetch-github](https://github.com/seppeljordan/nix-prefetch-github) を利用して取得。 ```bash $ nix-shell -p nix-prefetch-github $ nix-prefetch-github-latest-release --nix Ladicle tcardgen let pkgs = import {}; in pkgs.fetchFromGitHub { owner = "Ladicle"; repo = "tcardgen"; rev = "2222547ac37c2d6e1961b00acef3771f48ac8220"; hash = "sha256-6Z4SWpjdPMMCC6xm+xjSNAWQpO2FD91p+Mk9Y+Hh7AY="; } ``` ### 2. 手元のBuildが通ることを確認 {#2-dot-手元のbuildが通ることを確認} `vendorHash` は空文字にしてbuildをするとエラーメッセージに書いてあるのでそれを埋める。 ```bash $ nix build .#tcardgen $ ./result/bin/tcardgen --help Generate TwitterCard(OGP) images for your Hugo posts. Supported front-matters are title, author, categories, tags, and date. Usage: tcardgen [-f ] [-o ] [-t