なつねこメモ

主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ 書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。

Git LFS サーバーを自作して、 S3 などのストレージにデータをストアしたい

みなさん Git LFS を使用していますか?
わたしは VRChat のアバタープロジェクトをまるまる GitHub へあげているついでに、 Git LFS で 3D モデルやテクスチャーも状態を保存しています。
そこで困るのが、 GitHub のストレージ課金です。 50GB で 60USD/year かかるのですが、これをオンラインストレージで換算すると、微妙に高くない?となります (Wasabi とか 72USD/year で 1TB だしね)。

例えば、ウチのプロジェクトの場合、大きいもので数十 GB くらいあったりするので、それをアップロードするとオシマイです。
そうなるとどんどん高くなって行っちゃうので、個人的には S3 とかにバックアップしたい気持ちがあります。ということで、 Git LFS サーバーを実装してみました。

といっても必要なのは1つだけで、以下のエンドポイントを実装すれば OK です。

POST /objects/batch

わたしは認証機能を付けたかったので、 POST /:user/:repository/objects/batch として実装していますが、基本的にこのエンドポイントだけ実装してあげれば、 Git LFS サーバーとして必要最低限の機能が動くようになります。
レスポンスも簡単で、次のような型を持つレスポンスを返せば OK です。

type GitLfsObject = {
  oid: string;
  size: number;
  authenticated: boolean;
  actions: {
    upload: {
      href: string;
      expiresIn?: number;
      headers?: any;
    },
    download: {
      href: string;
      expiresIn?: number;
      headers?: any;
    }
  }
};

type GitLfsResponse = {
  transfer: "basic" | "lfs-standalone-file" | "ssh";
  objects: GitLfsObject[];
};

簡単ですね。
Git LFS クライアントからのリクエストは以下の通り。

type GitLfsRequest = {
  operation: "verify" | "upload" | "download";
  transfers: string[];
  objects: { oid: string; size: number; }[];
};

基本的には、 operation == upload のときには、 actions.uploadoperation == download のときには actions.download を返してあげれば問題ありません。
それぞれの中身も、 href に、それぞれの操作に対応した S3 の Pre-Signed URL を詰め込んで返せば、 Git LFS クライアントが良い感じにしてくれます。

ということで、 Git LFS サーバーの実装についてお話ししてみました。
具体的なコードについて知りたい場合は、下記リポジトリに Vercel にデプロイ可能なコードを置いているので、参考にしてみて下さい。