macOS
イベント
該当するコンテンツが見つかりませんでした
マガジン
該当するコンテンツが見つかりませんでした
技術ブログ
はじめに # Deno 2.9 リリースおめでとうございます。 Deno 2.9 | Deno Electron 大好きな自分としても気になるのはやはり Deno Desktop です。 Tauri と同様 WebView をバックエンドにする構成と Electron と同様 Chromium ベースの構成を選べるとのことで、これは試すしかないと思いました。 公式ドキュメントは以下にあります。 https://docs.deno.com/runtime/desktop/ --> Caution Deno ブログには以下のように書かれており、2.9 時点ではデスクトップ機能は実験的段階です。 deno desktop is experimental in 2.9. The surface described here is stabilizing and some platform features are still landing. 使ってみる # まずは 2.9 にアップグレードしておきます [1] 。 deno upgrade main.ts に Deno.serve を使って普通にサーバープログラムを書きます。 main.ts Deno.serve(() => new Response( "<!DOCTYPE html><h1>Hello from Deno desktop </h1>", { headers: { "content-type": "text/html" } }, ) ); 同じディレクトリで deno desktop main.ts を実行します。 $ deno desktop main.ts ⚠ deno desktop is experimental and subject to change Check main.ts Compile main.ts to hello.dylib Embedded Files hello.dylib └── main.ts (430B) Files: 1.91KB Metadata: 1.38KB Remote modules: 12B Downloading laufey webview backend for aarch64-apple-darwin (v0.4.0) Download laufey-webview-aarch64-apple-darwin.tar.gz 97.44KiB/97.44KiB Codesigning bundle with identity "-" hello.app/Contents/MacOS/laufey_webview: replacing existing signature hello.app/Contents/MacOS/hello.dylib: replacing existing signature Bundle hello.app 最後の出力で、ルートに hello.app (macOS のアプリ実行ファイル)が生成されており、起動できます(Windows の場合は、hello.exe が生成されます)。 Deno のコンセプト通り、追加のモジュールや設定なし(Out of the box)でデスクトップアプリが生成されました。 Deno Desktop の開発体験 # HMR (Hot Module Replacement) オプション付きで起動することで、ローカルの開発サーバを立ち上げ、コード変更を検知してアプリ内容を即時更新してくれます。 deno desktop --hmr main.ts ⚠ deno desktop is experimental and subject to change Compile main.ts to file:///Users/kondoumh/Library/Caches/deno/desktop/5f4a00908e99d886/hello.dylib Embedded Files hello.dylib └── main.ts (422B) Files: 1.9KB Metadata: 1.38KB Remote modules: 12B Running desktop app with HMR (watching /Users/kondoumh/dev/deno-study/desktop/hello) Runtime loaded successfully from: /Users/kondoumh/Library/Caches/deno/desktop/5f4a00908e99d886/hello.dylib Runtime started [desktop] dylib path: "/Users/kondoumh/Library/Caches/deno/desktop/5f4a00908e99d886/hello.dylib" Listening on http://127.0.0.1:52958/ main.ts のコードを書き換えると、保存後すぐに画面へ反映されます。 --> Information Electron では HMR は標準では利用できず、別途 Forge などで開発サーバーを起動する必要があります。 https://developer.mamezou-tech.com/blogs/2024/01/29/electron-forge-introduction/ UI の ローカル HTTP サービスによる実現 # Electron (Forge など) ではローカルサーバーの利用は開発時が中心で、配布後は file:// でアセットを読む構成が一般的です。 これに対し Deno Desktop は、配布後のバイナリでもローカル HTTP サーバーを内部起動し、空きポートを自動割り当てして UI を描画します。サーバーはプロセス内で閉じており、外部公開はされません。ポート衝突を意識せずに済むのも良い点です。 この「開発時もビルド済みバイナリでも、同じ HTTP 実行モデルで UI を提供する」設計により、 開発時とデプロイ時の挙動に差がない コンテンツはブラウザとデスクトップで同じ動きをする Next.js などのフレームワークがそのままデスクトップアプリの中で動く といったメリットが得られます。 https://docs.deno.com/runtime/desktop/serving/ DevTools の起動 # Electron や Tauri と同様、DevTools によるデバッグが可能です。BrowserWindow を起動し、 openDevtools メソッドを呼ぶだけです。 const win = new Deno.BrowserWindow({ title: "My Deno Desktop App", width: 800, height: 600, }); win.openDevtools(); https://docs.deno.com/runtime/desktop/devtools/ --> Information いまのところ、DevTools のフルサポートはバックエンドを cef にしている時のみです。 以下のように、指定して起動する必要があります。 deno desktop --hmr --backend=cef main.ts バックエンドとフロントエンドの通信(Bindings) # Electron の IPC 通信は render.js と main.js を preload.js 経由でブリッジする必要があり、かなり面倒です。Deno デスクトップでは BrowserWindow にバインドした関数を bindings というグローバルオブジェクトにより簡単に呼び出すことができます。 Deno ランタイムとレンダリングバックエンドはスレッドやプロセスとして動作し、呼び出しはプロセス内チャネルを介して行われます。このサンプル構成ではソケットベースの IPC を直接扱わずに済むため、Electron の ipcMain / ipcRenderer、Tauri の invoke と比べて見通しよく書けるのが利点です。 実際のコードで見てみましょう。 const win = new Deno.BrowserWindow({ title: "Bindingsのテスト", width: 800, height: 600, }); // ========================================== // 1. バックエンド側:フロントから呼ばれる関数を登録 // ========================================== win.bind("getSystemInfo", async (userName) => { console.log(`[Deno側] フロントエンドから呼ばれました! 引数: ${userName}`); // Denoの機能を使ってOSの情報を取得 const denoVersion = Deno.version.deno; const os = Deno.build.os; // 少し重い処理をシミュレート(0.5秒待つ) await new Promise(resolve => setTimeout(resolve, 500)); // フロントエンドに返すデータ(JSON化できるものなら何でもOK) return { message: `こんにちは、${userName}さん!`, os: os, denoVersion: denoVersion }; }); // ========================================== // 2. フロントエンド側:画面のHTMLを返す // ========================================== Deno.serve(() => { const html = ` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Bindings Test</title> </head> <body> <h1>Deno Desktop Bindings</h1> <button id="btn">システム情報を取得</button> <pre id="result">ここに結果が出ます</pre> <script> // ボタンが押された時の処理 document.querySelector('#btn').addEventListener('click', async () => { const resultArea = document.getElementById('result'); resultArea.textContent = "取得中..."; try { // 💡 bindings を使ってバックエンドの関数を呼び出す const data = await bindings.getSystemInfo("kondoumh"); // 結果を画面に表示 resultArea.textContent = JSON.stringify(data, null, 2); } catch (error) { resultArea.textContent = "error: " + error.message; } }); </script> </body> </html> `; return new Response(html, { headers: { "content-type": "text/html" }, }); }); アプリ画面です。 システム情報を取得 ボタンをクリックするとしばらく呼び出し中になり、結果が表示されます。 結果が表示された状態。 アプリを起動しているバックエンドでは次のようにログが出ています。 [Deno側] フロントエンドから呼ばれました! 引数: kondoumh すごくシンプルです。OS のネイティブ機能と Web UI を簡単に連携できるのがいいですね。 https://docs.deno.com/runtime/desktop/bindings/ メニュー の利用 # アプリケーションメニューの実装。 BrowserWindow の setApplicationMenu メソッド内でメニューオブジェクトを定義して渡します。 BrowserWindow にイベントリスナーを登録してメニューがクリックされた時の振る舞いを実装します。 role などは Electron と同じですね。 win.setApplicationMenu([ { submenu: { label: "File", items: [ { item: { label: "New", id: "new", accelerator: "CmdOrCtrl+N", enabled: true, }, }, { item: { label: "Open…", id: "open", accelerator: "CmdOrCtrl+O", enabled: true, }, }, "separator", { item: { label: "Save", id: "save", accelerator: "CmdOrCtrl+S", enabled: true, }, }, { role: { role: "quit" } }, ], }, }, { submenu: { label: "Edit", items: [ { role: { role: "undo" } }, { role: { role: "redo" } }, "separator", { role: { role: "cut" } }, { role: { role: "copy" } }, { role: { role: "paste" } }, ], }, }, ]); win.addEventListener("menuclick", (e) => { const detail = (e as CustomEvent).detail; switch (detail.id) { case "new": console.log("New clicked"); break; case "open": console.log("Open clicked"); break; case "save": console.log("Save clicked"); break; } }); コンテキストメニューの実装。 Deno.MenuItem の配列を作成して、BrowserWindow の showContextMenu に座標とともに渡します。 const contextMenu: Deno.MenuItem[] = [ { item: { label: "Copy", id: "copy", enabled: true } }, { item: { label: "Paste", id: "paste", enabled: true } }, "separator", { item: { label: "Properties…", id: "props", enabled: true } }, ]; // Trigger from a right-click. The webview may not forward the browser // `contextmenu` event, so handle the secondary mouse button on the window. win.addEventListener("mousedown", (e) => { if (e.button === 2) { win.showContextMenu(e.clientX, e.clientY, contextMenu); } }); win.addEventListener("contextmenuclick", (e) => { if (e.detail.id === "copy") { console.log("Copy clicked"); } if (e.detail.id === "paste") { console.log("Paste clicked"); } if (e.detail.id === "props") { console.log("Properties clicked"); } }); --> Information ここではメニューのクリックイベントでログを出力していますが、ログ自体はアプリを起動しているターミナル側に出ますのでご注意ください。 https://docs.deno.com/runtime/desktop/menus/ フレームワークを利用した開発 # Deno.serve() を利用したサンプルを見てきましたが、Deno デスクトップでは、以下のフレームワークとともに利用可能です。これらのプロジェクトのディレクトリで deno desktop を起動すると、フレームワークを自動検出してアプリを構成します。多くのモダンフレームワークがサポートされています。 Next.js Astro Fresh Remix Nuxt SvelteKit SolidStart TanStack Start Vite ローカルで動いてるのに SSR を使うというのがなんとも不思議な感じですが、ちゃんと動いてセキュアであればヨシ!という感じでしょうか。 https://docs.deno.com/examples/next_tutorial/ Next.js のアプリを作成します。 deno run -A npm:create-next-app@latest 作成したプロジェクトディレクトリへ移動して実行します。 cd <project-dir> deno desktop -A Next.js のアプリが、外部サーバーなしでまるっとデスクトップ内で動いてるのは不思議な感じです。 https://docs.deno.com/runtime/desktop/frameworks/ バックエンドの選択について # デスクトップアプリは配布するバイナリのサイズも重要です。小さいに越したことはありません。 Electron は Chromium を内包するため、インストールされたバイナリサイズは300MBぐらいの大きさになったりします。 Deno Desktop の場合、OS にプリインストールされている WebView を使えば70MB程度です。CEF(Chromium) だとやはり300MB程度になります。 OS 依存の WebView だと、Windows と Mac で微妙に CSS や JS の挙動が変わるクロスブラウザ問題が発生するため、そのための対応やテストも必要になります。機能が少ないうちは WebView でもいいかもしれませんが、機能が増えてくるとテストの手間も何倍にもなっていきます。 Deno Desktop の場合、最初は軽量な WebView でスタートし、クロスブラウザが重荷になってきたら、ちょっと配布サイズは大きくなるけど、CEF にスイッチできるのがいいかなと思います。 https://docs.deno.com/runtime/desktop/backends/ --> Information Tauri だとこの辺、Servo ベースの自前 WebView プロジェクト Verso 待ちですが、Deno は既存の Chromium を選択可能にしているあたり、現時点での割り切りが感じられますね。 Electron との比較 # 既存 Web アプリをデスクトップ化したいユースケースでは、Deno Desktop はかなり有力です。 一方で、Electron の WebContentsView のような高度なマルチビュー構成を前提にしている場合は、現時点では Electron のほうが適しています。たとえば VS Code や Figma のように、複数ビューを細かく制御するタイプのアプリです。 ざっくり整理すると次のような感触です。 単一ウィンドウ中心 + 既存 Web 資産活用: Deno Desktop はかなり良い 複雑なウィンドウ/ビュー管理: Electron が依然強い --> Information マルチビュー構成の対応の弱さは Tauri も同様です。 https://developer.mamezou-tech.com/blogs/2025/12/01/porting-an-electron-app-to-tauri2/ Electron の WebContensView 構成については以下の記事をご参照ください。 /blogs/2024/08/28/electron-webcontentsview-app-structure/ https://docs.deno.com/runtime/desktop/comparison/ さいごに # 以上、Deno Desktop 機能を一通り試しました。 Out of the box でここまでデスクトップ開発体験が整っているのは率直に驚きです。タスクトレイやメニュー、Bindings など、アプリらしさを出すための API が最初から揃っているのも好印象でした。 Tauri と違ってアプリ側をすべて TypeScript で書けるため、既存 Web アプリをベースに「メニューやタスクトレイを追加し、OS 機能と連携する」用途ではかなり相性がよいと感じます。最小構成なら、デスクトップアプリ化自体は1時間もかからないはずです。 --> Information Tauri も JS の API を生やして、Rust 知らない勢を取り込もうとしてはいます。 Deno のキラー機能になる可能性もありますね。experimental から安定版へ向けて、今後の熟成がとても楽しみな機能です。 2026年7月6日現在の最新は 2.9.1 です。 ↩︎
計測データをもっと簡単に活用したいみなさん、 こんにちは、ソリューションアーキテクトの伊勢です。 収集された計測データを取得・加工してシステム連携するケースが増えてきました。 今回は自分で実装せず、AIエージェントにAPIの仕様を教えて実現してみます。 GeoJSONを生成し、地図サービスで可視化した結果をHTMLとして公開します。 はじめに intdash API やりたいこと Codexとは kepler.glとは やってみた 緯度経度を可視化 高度・速度を追加 表示レイヤー追加 写真サムネイルの追加 地図HTMLの公開 感想 うまくいったところ REST APIの理解、計測取得 軌跡GeoJSONを調整 S3でファイル公開 プロジェクト内を整理 試行錯誤したところ 写真データのサムネイル作成 kepler.glでの画像表示 おわりに はじめに intdash API 以前ご紹介した intdash API は保存済みの計測データをREST APIで扱えます。 このAPI仕様をAIエージェントに理解してもらいます。 tech.aptpod.co.jp やりたいこと 休みを取ってJR芸備線に乗ってきました。 JRで一番採算が取れない路線で廃線が議論されています。 tetsudokyogikai.net GPS計測データ、駅情報、写真を地図上に重ねて、旅の流れを見えるようにします。 データフロー 複数の入力データを元に地図サービスで可視化します。 地図に載せたい情報は以下です。 intdashのGPS(GNSS)計測データ タイムスタンプ 緯度経度 高度 速度 駅情報 緯度経度 駅名 旅の写真 撮影時刻 EXIFの緯度経度 サムネイル画像URL 画像キャプション 手作業でやると、API仕様の確認、データ取得、バイナリデコード、S3公開、インタフェース向けの調整など、地味な作業がたくさんあります。 今回はこの一連の作業を Codex に依頼しました。 Codexとは コードの読み書きやコマンド実行、ファイル生成、API調査などを行えるAIエージェントです。 1 今回のポイントは、単にコードを書いてもらうだけではありません。 intdash API仕様を確認する 実際にAPIへ接続してデータ構造を確認する プロトコルのペイロード仕様を読んでバイナリをデコードする 連携先で扱いやすいGeoJSONを検討する 写真のEXIFを読み、サムネイルを作り、S3に公開する AIエージェントはこういった「やりたいことは明確だけど、途中に細かい作業が多い」タスクを試行錯誤しつつ遂行してくれます。 kepler.glとは 位置情報データを地図上で可視化できるWebアプリケーションです。 GeoJSONやCSVなどをアップロードすると、点・線・ポリゴンなどの地理情報をレイヤーとして表示できます。 今回生成したGeoJSONをアップロードし、HTMLとしてエクスポートしました。 2 kepler.gl やってみた 緯度経度を可視化 今回の作業は、最初から細かい仕様をすべて決めていたわけではありません。 まずは、やりたいことをざっくり伝えました。 intdash計測データを元に kepler.glにアップロードするためのGeoJSONを生成してください。 入力データには intdash Motion v2 のGNSS計測データを取得します。 intdashのAPI仕様の認証、計測、計測データの取得方法を確認してください。 対象データはエッジ ise の日本時間2026/6/23に作成された複数の計測です。 一覧表示して確認させてください。 接続先サーバー環境情報はプロジェクトの .env に記載しています。 取得する計測データは以下のとおりです。 緯度経度 取得するデータ名は推定して候補を提示してください。 2D Vectorペイロードフォーマットで格納されています。 緯度と経度に分けて出力してください。 計測データには1つずつ タイムスタンプ が付与されています。 GeoJSONに出力してください。 タイムスタンプはYYYY-MM-DD hh:mm:ss.SSS形式でラベル属性としても出力してください。 この時点で伝えていたのは、対象データ、API仕様、だいたいの出力イメージです。 初回レスポンス 生成されたGeoJSON 出力されたGeoJSONをkepler.glのデモ画面にアップロードすると、早速軌跡が表示されました。 初回アップロード 途中で調整も依頼しました。 kepler.glで可視化できました。 いくつか修正してください。 ・可読性のため、geojsonをインデントフォーマットしてください。 ・timestampは日本時間ということがわかるようにしてください。 ・labelがtime属性と認識されていまっています。stringになるようにしてください。 高度・速度を追加 表示項目を追加してみました。 3 以下の変更を加えてください。 ・同計測から、高度・速度の計測データも取得して、項目として付与してください。 高度でグラデーション 速度でグラデーション 軌跡が北側に凸になっている箇所が高地で低速のようです。 表示レイヤー追加 intdash以外のデータも追加して表示します。 kepler.glにアップロードするGeoJSONを追加します。 インターネットからJR芸備線の駅の情報を取得してください 緯度経度 駅名 また、緯度経度のGeoJSONは線として出力するよう変更してください。 駅名レイヤー追加(下が北) 低速だった区間は備後落合駅の付近のようです。 写真サムネイルの追加 ローカルファイルからのGeoJSON生成も依頼しました。 プロジェクトの photo フォルダに旅の写真を置いてあります。 - QVGAサイズのサムネイルを作成 - EXIFから撮影時刻、緯度経度を抽出 - サムネイルをAWS S3にアップロード・公開URL取得 - サムネイルを画像認識してキャプションを生成 - 撮影時刻、緯度経度、キャプション、サムネイルURLからGeoJSONを作成 このように多岐にわたる作業を一手に依頼できるのも魅力です。 写真GeoJSONの生成 地図HTMLの公開 最後に地図の公開を依頼しました。 kepler.glからHTMLをダウンロードしてプロジェクトに置きました。 S3に配置して公開URLを取得してください。 また、プロジェクト内のフォルダを整理して古い不要ファイルは削除してください。 HTMLファイルの公開 今回できた地図HTMLをS3で公開 しています。 公開HTML 3種類のデータを重ねたものです。 4 intdashの走行軌跡 沿線の駅 旅写真のサムネイルとキャプション 軌跡は速度で色分けしています。 「どんなところで何を見たのか」振り返れる旅の記録になりました。 感想 うまくいったところ REST APIの理解、計測取得 CodexによるintdashのREST API仕様・アクセス方法を理解は滞りなく進みました。 プロジェクト/エッジ/計測を具体的なUUIDで指定しなかったので、 何らかの追加指示が必要になるかと思っていたのですが、 指示に該当する計測を自動で認識してくれました。 また、計測に含まれるデータIDを見て、緯度経度は gnss_coordinates と推定してくれました。 実際のデータを数件取得して、仕様通りのデコードができるか、値が妥当かを確認してくれました。 データIDの確認 gnss_coordinates は 2D Vector という2値形式で格納されています。 仕様では、X/YそれぞれがIEEE754 64bit浮動小数点数で、ビッグエンディアンです。 実際のAPIレスポンスでは、データ本体は以下のようにBase64文字列として返ってきます。 " data ": { " d ": " QEFVC8tEtA1AYJfd1+VRuw== " } 「Base64をデコードしてください」と明示的に指示したわけではありませんが、CodexがAPIレスポンスの形と仕様を見て、 data.d をBase64デコードし、16 bytesをビッグエンディアンの double として読む必要がある、と解釈してくれました。 " features ": [ { " type ": " Feature ", " geometry ": { " type ": " Point ", " coordinates ": [ 132.74583048619192 , 34.66442242483763 ] } , なお、GeoJSONの座標は [longitude, latitude] の順なので、出力時には入れ替えてもくれました。このあたりは人間が手でやると間違えやすいところです。 軌跡GeoJSONを調整 最初はGNSSデータの緯度経度を点として出力しました。 駅位置の点とあわせて表示すると見づらいため、線として表示するよう変更したところ、色分けしやすいように平均高度・平均速度などを付与してくれました。 " properties ": { " altitude_start ": 198.84193365530137 , " altitude_end ": 196.83770584185308 , " altitude_avg ": 197.8398197485772 , " speed_start ": 0.9940862059593201 , " speed_end ": 0.5033005893230439 , " speed_avg ": 0.748693397641182 } S3でファイル公開 サムネイルとkepler.glのHTMLをAWS S3に配置しました。 PCに導入済みのAWS CLIを使ってもらいましたが、アクセス情報はローカルのプロファルを使うよう指示しただけですんなり配置できました。 S3のサムネイルファイル プロジェクト内を整理 生成物が増えてきたのでプロジェクト内を整理しました。 不要になった中間ファイルは削除しました。 リファクタリングも気軽に行えます。 試行錯誤したところ 写真データのサムネイル作成 HEICからサムネイルを作るところで少し詰まりました。 PCに入っていた ffmpeg で変換してもらいましたが、写真の一部だけが切り抜かれたようなサムネイルになりました。 最終的には macOSのQuickLookを使って、元写真全体からサムネイルを生成する方式に変更しました。 結果として、横写真は 320x240 、縦写真は 180x240 のように、写真全体を保持したサムネイルになりました。 サムネイル生成修正 kepler.glでの画像表示 最初は画像URLが単なる文字列として表示されてしまいました。 そこで、kepler.glのTooltipで画像として扱えるように "<img>-tooltip" というプロパティ名に変更しました。 "<img>-tooltip": "https://example.s3.ap-northeast-1.amazonaws.com/xxx.png" サムネイル表示 おわりに 今回の実行結果をTech Blog記事を執筆します。 タイトルは AIエージェントが旅の軌跡まとめてくれるUX です。 過去の私の記事の章立てを参考にドラフトを作成してください。 https://tech.aptpod.co.jp/entry/2025/10/31/160000 今回は、Codexを使って思い出をまとめるユーザー体験の旅に出かけました。 やっていること自体は、APIからデータを取得して、GeoJSONにして、地図に載せるだけですが、実際には細かい判断がたくさんあります。 API仕様のどこを読めばよいか どのデータ名が緯度経度なのか バイナリペイロードをどうデコードするか 連携サービスではどういうデータ形式が扱いやすいか 写真サムネイルが正しく表示されているか S3の公開設定が正しいか これらをCodexと会話しながら進められるため、安心感が伴いました。 特に、一度表示してから方式修正を繰り返せたのが実用的でした。 AIエージェントは、最初から完璧な成果物を一発で出すというより、状況に合わせて柔軟に調整する随伴者でいてもらうのがよさそうです。 5 心強いから、自然に一歩を踏み出せる。そんな景色が見えてきました。 今回はVS Codeの拡張機能実装を利用しています。 ↩ 地図サービスのAPIによっては、データ連携もできます。 ↩ GNSS内の項目は同じタイムスタンプが付与されているので一意に紐付けが可能です。 ↩ ポイントにカーソルを当てても一度では写真が表示されないことがあるようです。 ↩ 生成AIの回答や生成物には誤りが含まれる可能性があります。API仕様、データ件数、公開設定などは実際に確認しながら利用します。 ↩
こんにちは。クロスイノベーション本部 AIデータテクノロジーユニット AIトランスフォーメーションセンターの青木 尚人です。 本記事では、SOPS を利用してチーム開発の環境変数管理を標準化する方法を紹介します。 はじめに チーム開発で .env を使っていると、次のような運用になりがちです。 .env の実際の値を Slack や Teams で共有する 新規メンバーが入るたびに、誰かが .env を手作業で渡す .env.example はあるが、実際の値とはずれている どの値が最新なのかわからない 秘密情報とそうでない値が混ざっている .env を誤って Git にコミットしてしまう そこで今回は、 SOPS + Azure Key Vault + mise を使って、暗号化された .env を Git 管理できるようにします。 この記事で紹介するのは、次のような手順での環境変数の管理方法です。 SOPS で .env 形式のファイルを暗号化する Azure Key Vault の Key を SOPS の暗号化・復号に使う mise で sops と azure-cli を導入する mise task で .env の復号・生成コマンドを標準化する この記事の前半では、まず SOPS を利用して暗号化された環境変数を作成します。 その後、mise task を使って .env の生成手順を標準化します。 本記事で紹介するプロジェクトで想定している最小の構成は以下です。 Existing Git Repository ├── .env.sops.env # SOPSで暗号化された.env。Git管理する ├── .env # 復号して生成する.env。Git管理しない ├── .sops.yaml # SOPSの暗号化設定。Git管理する └── mise.toml # ツールとタスク定義。Git管理する Azure Key Vault └── Key Vault Key # SOPSの暗号化・復号に使う鍵 SOPS とは SOPS は、YAML、JSON、ENV、INI などのファイルを暗号化して管理するためのツールです。 公式ドキュメント: https://getsops.io/docs/ GitHub リポジトリ: https://github.com/getsops/sops 一般的な暗号化ツールと違い、ファイル全体を単純にバイナリ化するのではなく、設定ファイルとして扱いやすい形で暗号化できます。 例えば .env 形式のファイルであれば、暗号化後もキー名は読める状態にしつつ、値だけを暗号化できます。 似た選択肢としては、Azure Key Vault Secret に環境変数を1つずつ保存する方法、 .env.example で項目だけ共有する方法、 .env ファイルの暗号化に特化したツールとして dotenvx もあります。 dotenvx は、既存の .env 運用に近い形で暗号化された .env ファイルを扱えるため、 .env を中心にシンプルに管理したい場合は有力な選択肢です。 一方、今回の構成では Azure Key Vault の Key を使って復号権限を Azure 側で制御したかったため、SOPS を採用しています。 SOPS は .env だけでなく YAML、JSON、INI など複数の設定ファイル形式を扱えるため、環境変数以外の設定ファイル管理にも広げやすい点もメリットです。 暗号化前の .env が次のような内容だったとします。 DATABASE_URL=postgresql://demo_user:demo_password@localhost:5432/demo_db API_TOKEN=dummy-api-token JWT_SECRET=dummy-jwt-secret SOPS で暗号化すると、次のようなファイルになります。 DATABASE_URL=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str] API_TOKEN=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str] JWT_SECRET=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str] この状態なら、暗号化された .env ファイルを Git 管理できます。 値は暗号化されているため、リポジトリに置いても平文の secret は見えません。 Azure Key Vault は何に使うのか 今回、Azure Key Vault は、環境変数そのものの保存先ではなく、SOPS がデータキーを保護するための Key Vault Key の管理基盤として使います。 Azure Key Vault では、主に次の3種類のオブジェクトを管理できます。 Key Secret Certificate このうち、今回使うのは Secret ではなく Key です。 環境変数を Azure Key Vault Secret に1つずつ保存する構成ではありません。 SOPS が暗号化・復号に使う鍵を、Azure Key Vault Key として管理します。 Azure Key Vault └── Key └── SOPSが.env.sops.envを暗号化・復号するために使う SOPS は、ファイル本体の値を暗号化し、その暗号化・復号に必要な情報をファイル内に保持します。 ただし、その復号には Azure Key Vault Key へのアクセス権が必要です。 そのため、次のような管理ができます。 Git には暗号化済みの .env.sops.env を配置する 復号できる人は Azure Key Vault の権限で制御する チャットで .env の平文を共有しない メンバー追加・削除時は Azure 側の権限を見直す ここが、この構成の重要なポイントです。 mise とは mise は、プロジェクトで使う CLI ツールのバージョン管理や、タスク定義をまとめて扱えるツールです。 SOPS と Azure CLI を各メンバーが個別にインストールしても、この構成は実現できます。 ただし、それだと次の問題が残ります。 SOPS がインストールされていない Azure CLI がインストールされていない メンバーごとにバージョンが異なる .env を生成するコマンドを毎回説明する必要がある mise を使うと、プロジェクトに必要な CLI ツールとタスクを mise.toml にまとめられます。 [tools] sops = "3.12.2" azure-cli = "2.84.0" これをプロジェクトに置いておけば、メンバーは次のコマンドで必要なツールをそろえられます。 mise install さらに、 .env を生成する処理も mise task にできます。 mise run env:render つまり mise を使う理由は、単にツールを入れたいからではありません。 チーム全員が同じコマンドで、同じ手順を実行できるようにするため です。 ここからは、mise のインストール手順から、Azure Key Vault と SOPS を使った環境変数の管理手順までを順に紹介します。 mise をインストールする macOS では Homebrew でインストールできます。 brew install mise zsh を使っている場合は、シェルに mise を有効化する設定を追加します。 echo 'eval "$(mise activate zsh)"' >> ~/.zshrc source ~/.zshrc インストールできたか確認します。 mise --version macOS 以外のインストール方法は、公式ドキュメントを参照してください。 https://mise.jdx.dev/getting-started.html mise.toml に sops と azure-cli を追加する 既存プロジェクトのルートに mise.toml を用意します。 すでに mise.toml がある場合は、既存の [tools] に追記してください。 [tools] sops = "3.12.2" azure-cli = "2.84.0" コマンドで追加する場合は、以下のようにします。 mise use sops@3.12.2 mise use azure-cli@2.84.0 その後、ツールをインストールします。 mise install 確認します。 sops --version az version Azure にログインする SOPS が Azure Key Vault を使って復号するには、ローカル端末が Azure に認証済みである必要があります。 まず Azure にログインします。 az login 現在のサブスクリプションを確認します。 az account show -o table 必要であれば、利用するサブスクリプションに切り替えます。 az account set --subscription "<subscription-id-or-name>" Azure Key Vault と Key を作成する すでにチームで利用している Key Vault がある場合は、既存の Key Vault に SOPS 用の Key を追加しても構いません。 Key Vault の作成 Azure Portal から操作する場合は以下のようになります。 トップ画面からキーコンテナーを選択します。 作成ボタンを押下して、キーコンテナーを作成します。 基本タブでリソースグループとリージョンを選択します。 アクセス制御タブでは「Azureロールベースのアクセス制御(RBAC)」を選択してください。 ネットワークタブでは必要に応じてアクセス制御を設定します。 ここではデフォルトの「すべてのネットワークからのアクセスを許可する」を選択します。 ※実運用では要件に合わせて制限してください。特に本番 secret に関わる Key Vault では、ネットワーク制限を含めた設計が必要です。 最後に確認タブで設定を確認し、作成ボタンを押下してキーコンテナーを作成します。 Key Vault のアクセス制御で権限を付与する 次に、Key Vault の Key を使えるように、アクセス制御で権限を付与します。 アクセス制御(IAM)タブを開き、ロールの割り当てを追加します。 Key Vault の Key を作成・編集する管理者には「キー コンテナー暗号化責任者」を割り当てます。 一方で、一般的な開発メンバーが環境変数の暗号化・復号に利用するだけであれば、「キー コンテナー暗号化ユーザー」を割り当てるのが適切です。 ここでは、Key を作成するために「キー コンテナー暗号化責任者」のロールを自身に割り当てます。 SOPS 用の Key の作成 [オブジェクト > キー] のタブから「+ 生成/インポート」を押下します。 名前や RSA キーサイズを選択して、環境変数を暗号化する SOPS 用の Key を作成します。 作成した Key の ID を取得します。 出力例は以下のとおりです。 https://kv-sops-xxxx.vault.azure.net/keys/sops-env-key/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx この Key ID を SOPS の設定で使います。 .sops.yaml を追加する プロジェクトルートに .sops.yaml を追加します。 creation_rules : - path_regex : \.env\.sops\.env$ azure_keyvault : - https://kv-sops-xxxx.vault.azure.net/keys/sops-env-key/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx この設定により、 .env.sops.env という名前のファイルを SOPS で作成するときに、Azure Key Vault の Key が使われます。 暗号化前の .env を用意する ここでは、既存の .env から secret を含む値を暗号化する想定で進めます。 記事用の例ではダミー値を使います。 .env.plain という名前で、次のような内容を用意します。 DATABASE_URL=postgresql://demo_user:demo_password@localhost:5432/demo_db API_TOKEN=dummy-api-token JWT_SECRET=dummy-jwt-secret SOPS で .env を暗号化する .env.plain を SOPS で暗号化し、 .env.sops.env を作成します。 sops encrypt \ --filename-override .env.sops.env \ --input-type dotenv \ --output-type dotenv \ .env.plain > .env.sops.env 暗号化後のファイルを確認します。 cat .env.sops.env 次のように値が ENC[...] 形式になっていれば成功です。 DATABASE_URL=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str] API_TOKEN=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str] JWT_SECRET=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str] 暗号化前の一時ファイルは削除します。 rm .env.plain この時点で、Git 管理する対象は .env.sops.env です。 平文の .env や .env.plain ではありません。 復号して .env を生成する 復号できるか確認します。 sops decrypt .env.sops.env 以下のような出力が得られれば成功です。 DATABASE_URL=postgresql://demo_user:demo_password@localhost:5432/demo_db API_TOKEN=dummy-api-token JWT_SECRET=dummy-jwt-secret 問題なければ、 .env に出力します。 sops decrypt .env.sops.env > .env chmod 600 .env これでアプリケーションが読む .env が生成されます。 cat .env 出力例は以下のとおりです。 DATABASE_URL=postgresql://demo_user:demo_password@localhost:5432/demo_db API_TOKEN=dummy-api-token JWT_SECRET=dummy-jwt-secret mise task で .env 生成を標準化する 毎回 sops decrypt .env.sops.env > .env と入力するのは手間です。 そこで、 mise.toml にタスクを追加します。 [tasks."env:render"] description = "Generate .env from encrypted env file" run = ''' set -euo pipefail sops decrypt .env.sops.env > .env chmod 600 .env echo "generated: .env" ''' [tasks."env:decrypt"] description = "Print decrypted env to stdout" run = "sops decrypt .env.sops.env" これで、開発者は次のコマンドだけで .env を生成できます。 mise run env:render 以下のような出力が得られれば成功です。 % mise run env:render [env:render] $ sops decrypt .env.sops.env > .env generated: .env 復号結果を標準出力で確認したい場合は、次を使います。 mise run env:decrypt 以下のような結果が得られれば成功です。 % mise run env:decrypt [env:decrypt] $ sops decrypt .env.sops.env DATABASE_URL=postgresql://demo_user:demo_password@localhost:5432/demo_db API_TOKEN=dummy-api-token JWT_SECRET=dummy-jwt-secret VSCode で暗号化された .env を編集する 暗号化済みファイルを編集する場合は、 sops edit を使います。 VSCode で編集する場合は、次のようにします。 SOPS_EDITOR='code --wait' sops edit .env.sops.env 実行すると /var/folders/s7/86599dyd1g5clgw4f7l3d2840000gn/T/3982786129/.env.sops.env のような一時ファイルが VSCode で開かれます。 適宜編集して保存した後、ファイルを閉じると .env.sops.env が再暗号化されます。 これも mise task にしておくと便利です。 [tasks."env:edit"] description = "Edit encrypted env file with VSCode" run = "SOPS_EDITOR='code --wait' sops edit .env.sops.env" 実行します。 mise run env:edit EMBEDDING_API_TOKEN などの値を適宜書き換えて保存し、VSCode を閉じます。 .env.sops.env が再び暗号化された状態で保存されれば成功です。 DATABASE_URL=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str] API_TOKEN=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str] EMBEDDING_API_TOKEN=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str] JWT_SECRET=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str] 最終的な mise.toml 例 最小構成の mise.toml は次のようになります。 [tools] sops = "3.12.2" azure-cli = "2.84.0" [tasks."env:render"] description = "Generate .env from encrypted env file" run = ''' set -euo pipefail sops decrypt .env.sops.env > .env chmod 600 .env echo "generated: .env" ''' [tasks."env:decrypt"] description = "Print decrypted env to stdout" run = "sops decrypt .env.sops.env" [tasks."env:edit"] description = "Edit encrypted env file with VSCode" run = "SOPS_EDITOR='code --wait' sops edit .env.sops.env" このファイルをプロジェクトに置いておけば、開発者が覚えるコマンドは少なくなります。 mise install mise run env:render 新規メンバーが入ったときの手順 新規メンバーが入ったときは、次の手順でセットアップできます。 git clone <repository-url> cd <repository-name> mise install az login mise run env:render もちろん、「キー コンテナー暗号化ユーザー」などの Key Vault 権限は事前に必要です。 発展:shared / secret / local に分ける 実際のチーム運用では、すべての値を1つの .env.sops.env に入れるより、値の性質ごとに分けたほうが扱いやすい場合があります。 例えば、次のように分けます。 .env.shared # チームで共有してよい非秘密情報 .env.secret.sops.env # SOPSで暗号化した秘密情報 .env.local # 個人用の上書き設定 .env # 最終的に生成されるファイル この場合の考え方は次の通りです。 ファイル Git管理 用途 .env.shared する チームで共有してよい非秘密情報 .env.secret.sops.env する SOPSで暗号化した秘密情報 .env.local しない 個人のローカル上書き設定 .env しない アプリケーションが読む生成物 この構成にすると、非機密情報の差分が読みやすくなります。 一方で、ファイルが増えるため、最小構成よりは運用ルールが必要になります。 例えば、mise task は次のようになります。 [tasks."env:render"] description = "Generate .env from shared, encrypted secret, and local env files" run = ''' set -euo pipefail tmp_secret="$(mktemp)" cleanup() { rm -f "$tmp_secret" } trap cleanup EXIT sops decrypt .env.secret.sops.env > "$tmp_secret" { cat .env.shared printf "\n" cat "$tmp_secret" if [ -f .env.local ]; then printf "\n" cat .env.local fi } > .env chmod 600 .env echo "generated: .env" ''' まとめ SOPS を使うと、 .env 形式のファイルを暗号化して Git 管理できます。 Azure Key Vault を組み合わせることで、復号できる人を Azure 側の権限で制御できます。 さらに mise を使うことで、SOPS と Azure CLI の導入、そして .env の生成コマンドをチームでそろえられます。 最小構成は次の通りです。 .env.sops.env # 暗号化された.env。Git管理する .env # 復号して生成する.env。Git管理しない .sops.yaml # SOPS設定 mise.toml # ツールとタスク定義 開発者が実行するコマンドは、最終的にはこれだけにできます。 az login mise install mise run env:render 環境変数の管理はチーム開発において重要な課題です。 いざ開発に入ると、 .env の値をチャットで共有してしまったり、誰が最新の値を持っているのかわからなくなったりします。 今回の構成を使うことで、Git 管理と Azure Key Vault の権限管理を組み合わせながら、チームで同じ手順で .env を生成できるようになります。 参考資料 SOPS のドキュメント https://getsops.io/docs/ SOPS の GitHub リポジトリ https://github.com/getsops/sops SOPS Azure KMS https://getsops.io/docs/usage/identities/azure-kms/ dotenvx Encryption https://dotenvx.com/docs/quickstart/encryption/ SOPS Config File https://getsops.io/docs/usage/identities/config-file/ mise のインストール方法 https://mise.jdx.dev/installing-mise.html 執筆: @aoki.naoto レビュー: @yamada.y ( Shodo で執筆されました )
動画
該当するコンテンツが見つかりませんでした







