Vol. 01
2026.06.11
2026.05.29 ZENN // TECH

個人制作の有料アプリでライセンス管理を本気でやった話

ORIGIN: ZENN.DEV READ ORIGINAL

個人開発のStable Diffusionを動かすアプリ「Umaru」を配布するにあたって、ライセンス管理を本気でやった記録を残す。

「個人制作だしそこまでやらなくていいか」と思いつつも、有料配布する以上は責任を持ちたいという気持ちで整備した感じ。結果的にはかなり体系的な仕組みになったので、同じく有料配布を考えているインディー開発者の参考になれば幸い。


Umaruとは

Stable Diffusion XLをGUIで扱えるデスクトップアプリ。
別記事で、Umaruについて詳細に書いてるので、見てもらえると俺が嬉しい。

https://zenn.dev/ena_dri/articles/666b999e607ab0

Tauri v2 + React/TS製のフロントエンドと、FastAPIサイドカーで動くPythonバックエンドで構成。

依存関係の内訳は大きく3層:

  • npm(Tauri/React周辺)
  • Rust crate(Tauriランタイム)
  • Python(torch / diffusers / transformers など機械学習ライブラリ群)

この3層それぞれでライセンスを把握する必要があった。


なぜここまでやったのか

有料配布するアプリに他者のコードを使う以上、そのライセンス要件を満たすのは最低限の義務だと今更ながらに思った。
特にPython側はML系ライブラリの依存が深く、Apache-2.0、MIT、BSD系が広く混在する。
プロダクト仕様を詰める時に「たぶん大丈夫」で済ませたくなかった。

もうひとつの理由は自分の記録として残したいこと。
後から「なぜこのライセンスを許可したのか」が分からなくなるのを防ぎたかった。
今後、別のプロダクトを走らせる際にも使えると思うからね。


仕組みの概要

許可ポリシーの明文化

まず「何を許可するか」をlicense-policy-notes.mdに文書化。

デフォルト許可のSPDX識別子:

SPDX 種別 バイナリ配布で許可している根拠
MIT permissive 著作権表示と免責条項を同梱すれば自由に再配布可
Apache-2.0 permissive 著作権表示・NOTICE引き継ぎを行えばバイナリ配布可
BSD-2-Clause / BSD-3-Clause permissive 著作権表示・免責条項のみで配布可
ISC permissive MITとほぼ同等
MPL-2.0 weak copyleft(ファイル単位) MPLライセンスのファイル自体を改変しなければ、本体(Umaru側)のソース公開は不要
Unicode-3.0 permissive Unicodeデータ向け(CLDR等)

MPL-2.0だけは扱いに注意が必要。MPLはリンク形態(静的/動的)に依存しない、ファイル単位のコピーレフトで、LGPLとは挙動が違う。「MPLで配布されているファイル自体を改変したら、その改変ファイルのソースを開示する」というのが本体の要件。Umaru側のコードはMPLにならない。

Umaruで実際にMPL-2.0が登場するのは以下のRust crateなど(いずれもUmaru側で改変していない、純粋に依存しているだけ):

  • cssparser / cssparser-macros / selectors / dtoa-short / option-ext(cargo推移依存)
  • Python側では certifi, fqdn, tqdmtqdmはMPL-2.0 AND MIT のデュアル)

Rust側は基本的に静的リンクだが、MPL-2.0ファイルを改変していないため要件を満たす。改変が発生したら、その改変したファイルだけソース公開すれば足りる、という整理。

これに加えて、配布前ゲートで個別判断したものを条件付き許可として整理。

条件付き許可:

SPDX 根拠 / 用途
CC-BY-4.0 Umaru内で観測されたのは caniuse-lite(browserslist 経由のビルド時devツール)1件のみ。出力バンドルには含まれないが、配布物には NOTICE.txt に著作者表示を記載
PSFL Python本体・typing_extensions / defusedxml 等。Python Software Foundation License
CNRI-Python regexなどで Apache-2.0 とAND結合される。PSFLに倣う
Zlib foldhashなど。改変時の出所明示のみ
Unlicense filelock / portpicker など。実質public domain
MIT-CMU / HPND Pillow系。MIT系のpermissiveとして扱う
0BSD / CC0-1.0 実質public domain。numpy内の一部ファイル等

pip metadataのoverride

Pythonはpipのメタデータが曲者で、Licenseフィールドが空欄だったり、ライセンス本文をそのまま詰め込んでいたりするパッケージが現に存在する。これらは自動判定でUNKNOWNになってしまう。

対応として、PyPIとリポジトリのLICENSEファイルを実際に確認した上で、既知パッケージを手動overrideする辞書を持った。

PACKAGE_LICENSE_OVERRIDES = {
    "aiosqlite": "MIT",
    "pillow": "MIT-CMU",
    "filelock": "Unlicense",
    "typing_extensions": "PSFL",
    # ... 25件ほど
}

override判断の運用ルール:

  • 必ずPyPIの公式ページと、配布物に同梱されているLICENSEファイルを目視確認する
  • 確認したバージョンを license-policy-notes.md に記録する
  • パッケージ更新で verdict=needs_review が出たら、PyPIとリポジトリのLICENSEを再確認してからoverride
  • 配布前ゲート(generate_licenses.py)は更新のたびに必ず再実行する

例えば pillow は公式LICENSEで MIT-CMU(HPNDと同等)と明記されているのを確認したうえで MIT-CMU にoverride。バージョンによってライセンス本文の表記が変わることがあるため、メジャー更新ごとに見直している。

配布前ゲートスクリプト

scripts/generate_licenses.pyを作成し、配布前に必ず走らせる仕組みに。

  • allowed / needs_review / blocked / runtime_missing の4種で判定
  • blocked / needs_review / runtime_missing のいずれかが1件でもあれば exit 1 で止まる
  • UMARU_PYTHON=<venv>/python を指定することで、torch等のランタイム依存を含むUmaru専用venvに対して監査をかけられる
  • UMARU_AUDIT_STRICT=1 を併用すると、UMARU_PYTHON未指定 かつ Umaru venvが見つからないときも exit 1 で止まる(グローバルpip環境の異物を誤って許可しないため)

runtime_missingの扱い

requirements.txt / requirements-sdxl.txt / pyproject.toml に書かれているのに、監査用Pythonに未導入の依存は runtime_missing として報告する。「未導入のためUNKNOWN」と「本当にライセンスが不明」を区別するため。

現在の残件:

runtime_missing: onnxruntime / not_installed_in_audit_python

これは backend/pyproject.toml[project.optional-dependencies] tagger に置いている onnxruntime==1.23.2。SDXLメイン用のvenvにはtagger extraを入れていないので、現状の監査では runtime_missing として残る。

onnxruntime 自体は MIT / Apache-2.0 / BSD-3-Clause のpermissive構成なので方針上は問題ない。配布前ゲートとしては、tagger機能を含めてリリースするビルドでは「tagger extraを入れたvenvを UMARU_PYTHON に指定して [allowed] を出してから配布」、tagger機能を切るビルドではoptional-dependencyから除外して監査対象から外す、というルールにしている。


現在のライセンス監査結果

最新の LICENSES/third-party-licenses.jsoncounts

total           : 792件(npm + cargo + python)
allowed         : 791件
blocked         : 0件
needs_review    : 0件
runtime_missing : 1件(onnxruntime / tagger extra未導入)

npm(React/Tauri周辺)、Rust crate(Tauriランタイム)、Python(ML系)の全依存について [allowed] であることをUmaru専用venvに対する監査で確認している(python_audit.source = "app_data")。


ユーザーへの透明性

Umaru本体にはモデル重みを同梱しない
ユーザーが追加するモデル・LoRA・辞書データのライセンス遵守は最終的にユーザー責任になるが、アプリ側でも次のように責任境界を明確化している:

  • LICENSES/model-license-notice.mdで「モデル・LoRA・辞書・学習ツールはユーザー責任」を明文化
  • 辞書・LoRA取り込み時に、取得元URL・ライセンス名・同意日時をローカルDBに保存
  • EULAで「ユーザーが追加するモデル等の利用条件はユーザー責任」を明示

「DBに保存して終わり」ではなく、UI・EULA・NOTICEの3層で同じことを伝える形にした。

また配布物には LICENSES/third-party-licenses.txtNOTICE.txt を同梱し、CC-BY-4.0やApache-2.0の表示要件を満たした。


同じ仕組みを作りたい人へ(実践メモ)

これから自分でやってみる人向けに、最低限のステップ:

  1. 依存一覧を取り出す
    • npm: package-lock.jsonpackages 単位でなめる
    • cargo: cargo metadata --format-version 1 --locked
    • python: importlib.metadata.distributions()pip show は Classifier の連続行を拾えず UNKNOWN を量産するので非推奨)
  2. SPDX識別子に正規化する
    • Apache 2.0 License BSD 3-Clause License 等の表記揺れを正規化テーブルで吸収
    • A OR B A AND B A WITH exception の式を再帰的に評価
  3. 許可リストと禁止リストで判定
    • permissive系を allowed、GPL/AGPL/LGPLを blocked でまず大枠を決める
    • ORに1つでも allowed があれば許可、ANDは全部 allowed でないと許可しない、というルールにしておくとほぼ詰まらない
  4. 判定根拠を Markdown に残す
    • 「なぜこのSPDXを許可にしたか」「どのoverrideを何のバージョンで確認したか」を残す
    • これが半年後の自分を救う

仕組み自体は数百行のPythonで作れる。重いのはコードではなく**「許可した根拠を言語化する」**ところ。


やってみての感想

正直、最初は「個人制作でここまでやる必要ある?」と思った。でも整備してみると、どのライブラリに何の制約があるかを自分が把握できているという安心感が全然違った。

特にPythonのML系は依存が深く、気づかないところで微妙なライセンスが混入していることがある。スクリプトで自動チェックする仕組みにしておけば、パッケージ更新のたびに確認する手間も省ける。

結局のところ、成果物にコードは見えてこない。
誰かに突っ込まれて初めて露呈するものである。

だからこそ、誠意を、というお話でした。


関連