大企業のプロキシ環境で、Nix (Twist)を使ってEmacsをインストールする

[昨年のAdvent Calendarの記事](https://qiita.com/akirak/items/9920616bd3eff26eb6c0)で、NixでEmacs Lispパッケージをビルドする[Twist](https://github.com/emacs-twist/twist.nix)を紹介しました。今年の記事では、Twistを使った、大企業のプロキシ環境でも使えるEmacsのインストール方法を紹介します。Twistを使うことで、客先常駐のSES案件でも、あなた自身のemacs.d/init.elの設定でEmacsを使うことが技術的には可能になります（ただし、Emacsを動かすことが許可されるかどうかはまた別の問題です）。

大企業はしばしば、外部のネットワークへのアクセスをブロックするプロキシを引いており、協力会社（下請け）の社員が利用できる機能は制限されています。おそらくは、いたちごっこのような形で、大企業の情報システム部門も制限の方法を進歩させてきたでしょう。今回は、2023年の客先常駐の現場に入った私が、常駐先の就業環境においてどのようにネットワークが制限されていたか、そしてその制限をどのようにNixでworkaroundすることができたかをお伝えしようと思います。

## 大企業のネットワーク環境はどのように制限されているか

以下で説明するのは大企業のネットワーク環境の一例です。実際の環境は、お勤めまたは出向先の企業によりますし、本記事の内容が該当しない場合もあるでしょう。

大企業のプロキシ環境では(behind a corporate proxy)、ホスト単位での制限（たとえばオンラインストレージを提供するサービスのサイトはブラウザで閲覧できない）だけでなく、プロトコル単位での制限もあります。そもそも前提として、指定されたプロキシを通さないと外部のサービスにアクセスできませんし、SSLの証明書も必要になります。

たとえば、以下の事象に遭遇しました。JTCですので、端末はWindowsです。

-   Microsoft Storeからソフトウェアをインストールできない。（Microsoft Storeのアプリケーションにはプロキシ設定が適用されていないためだと思われる。）
-   GitHubのリポジトリを、HTTPSでcloneすることができない。git-cloneを実行すると、"Received HTTP code 407 from proxy after CONNECT"というエラーになる。
-   外部サイトへのpingができない。（ICMPプロトコルがブロックされている。）

特にHTTPSでのgit-cloneができないというのは、広範囲に影響します。たとえば、様々なプログラミング言語のバージョンを管理するのに使える[asdf](https://github.com/asdf-vm/asdf)はエラーが出てリポジトリの同期に失敗しました。Pythonの[rye](https://github.com/mitsuhiko/rye)も使えませんでした。

プログラミング言語のライブラリの中には実行時にパッケージをダウンロードするものもあり（たとえばプラットフォーム依存のバイナリをインストールするなど）、その際にプロキシ設定や証明書に起因するエラーが発生することもあります。実行時にファイルをダウンロードするライブラリとしては、[sql.js](https://sql.js.org/#/)や、Elixirの[Livebook](https://livebook.dev/)の中で使われることの多い[Explorer](https://github.com/elixir-explorer/explorer)などがあります。こういう場合はそれぞれのライブラリ固有のworkaroundによって解決できるようです。（ただし、実行時にどこかのサーバからファイルをダウンロードしてチェックサムも検証せず、任意のコードを実行可能にしてしまうライブラリ・フレームワークは、Nix使いとしては「純粋でない」ので気持ち悪いと感じます。）

ソフトウェアをインストールするための最も確実な方法は、ブラウザでファイルをダウンロードする、昔からおなじみの方法でした。JTCの情シス的には、Windowsの共有サーバから指定されたバージョンのプログラムをインストールするのが正規の手順なのでしょうが、実際には業務上必要なソフトウェアの最新版を外部のサイトからダウンロードしインストールするよう指示が出されることもあります。ブラウザ経由のダウンロードまで制限するとさらに業務効率が下がるでしょう。実際の運用は、運用次第であるようです。

ともかく、状況から判断して利用に問題がなさそうなVagrant + VirtualBoxに[VcXsrv](https://sourceforge.net/projects/vcxsrv/)を組み合わせて、2010年代式にLinuxのVM上でEmacsを動かすことにしました（WSLは色々と障壁がありそうだったのでパス）。 Debian系のLinuxディストリビューションを起動した後、VMから外部のインターネットに接続するために、プロキシの設定と証明書の追加、それにOpenSSLのデフォルトセキュリティレベルの変更は必要でした。 これらの手順はすぐにVagrantの[Ansible Local Provisioner](https://developer.hashicorp.com/vagrant/docs/provisioning/ansible_local)で自動化しました。 aptでパッケージをインストールすることはできたほか、嬉しいことに[Nixパッケージマネージャ](https://nixos.org/download#download-nix)も通常通りの手順でインストールすることができました。

## Nixで、Emacs環境を丸ごとtarballにする

VMにNixがインストールできたので、私の[emacs-config](https://github.com/akirak/emacs-config)が使えるかと思いきや、まだ障壁はありました。

NixOSは、基本的にはGentooと同様に、すべてのパッケージをソースからビルドするsource-basedのディストリビューションです。 ただし、ハッシュ値が一致するバイナリキャッシュが所定のサーバ（substitutersという）にある場合は、キャッシュを利用してビルド時間を節約することができる仕組みになっています。

今回のネットワーク環境では、Nixパッケージマネージャーはインストールできたものの、NixOS標準のキャッシュサーバ（​`cache.nixos.org`​）からファイルをダウンロードすることができませんでした。そのため、coreutilsのような基本的なパッケージさえソースからビルドする必要があり、現実的な時間内でビルドを終えることは不可能に思えました。

職場でビルドするのは諦め、代わりに採用したのが「パッケージを依存関係も含め全部まとめて一つのtarballにする」方法です。 以下で概要を説明します。

まず、私のemacs-configに必要なすべてのパッケージ（requisites）の、Nix store上のファイルパスの一覧を出力します。

```bash
nix-store -qR "$share" "$emacsEnv"
```

ここで、​`$share`​は「init.elとearly-init.elが入ったshare/emacsディレクトリのderivation」、​`emacsEnv`​は「私のEmacsパッケージ環境」ですが、詳しい説明は省きます。

次に、これらのすべてのrequisitesを、​`nix-store --export`​コマンドでserializeします。serializeされた結果は、後で​`nix-store --import`​コマンドを使って別のマシン（のNix store）に取り込むことができます。

```bash
nix-store --export FILES... > "$archive"
```

最後に、これらすべてのディレクトリを、一つのtarファイルにまとめて、zstdで圧縮します。

```bash
tar cf - "$archive" "$share" "$emacsEnv" | zstd > "$out"
```

こうして生成されたzstdファイルのサイズは、約1GBでした。 Emacsだけでなく、Emacsに必要な共有ライブラリや、Emacs Lispパッケージが実行時に必要とするripgrepなどのプログラムも入っているので、サイズが大きいです。 それでも、一台のマシンで全部ビルドし直すよりは短い時間でダウンロードすることができます。

このzstd圧縮されたtarballを生成する処理は、[twist-archiver](https://github.com/emacs-twist/twist-archiver)というNixのライブラリとして再利用可能にしました。

## GitHub Actionsを使ったリリースの自動化

こうして生成されたtarballをダウンロードできるように、どこか職場からアクセスできる場所に置かなければなりません。 一般的なオンラインストレージ等はブロックされています。 GitHubにはReleasesの機能がありますので、私は自分自身のemacs-configをGitHub ActionsのCIでリリースすることにしました。

私のemacs-configは頻繁に細かな改良・バグ修正が加えられdevelopブランチにpushされていますが、必ずしも職場で毎日最新のものを使う必要はありません（1GBものファイルをダウンロードするにはそれなりに待たないといけませんし）ので、developをmasterにマージしたときのみリリースが実行されるworkflowを用意しました（下スクリーンショットは[実行例](https://github.com/akirak/emacs-config/actions/runs/6998890298)です）。

![wayshot-20231225-230011.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151145/c676030b-7a77-985d-adab-cde11348f4fa.png)

workflowが成功すると、以下のようにリリースにzstdのファイルが追加されます。

![wayshot-20231225-230120.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151145/d449877a-8248-4808-e48d-95c91ccee79a.png)

こうして作られたtarballは、以下のようなコマンドでインストールすることができます。

```bash
zstdcat emacs-profile-default-x11-personalized-20231203-x86_64-linux.tar.zstd | tar xf -
## 展開された中のnarファイルをimport
nix-store --import < emacs-profile-default-x11-personalized.nar
## files.txtに記述されたパッケージ（init.el etc.）をprofileにインストール
xargs nix-env -i < files.txt
```

最後の​`nix-env`​コマンドによって、​`init.el`​と​`early-init.el`​が​`~/.nix-profile/share/emacs/`​にインストールされるので、初回インストール時のみ以下のコマンドで​`~/.config/emacs/`​にシンボリックリンクを作成する必要があります。

```bash
mkdir -p ~/.config/emacs
cd ~/.config/emacs
for file in init.el early-init.el; do
    ln -s ~/.nix-profile/share/emacs/$file
done
```

再インストールをするごとにNixストア(`/nix/store/`)が肥大化してVMのストレージが消費されていきますが、以下のコマンドでGCしてストレージを解放することができます。

```bash
nix-env --list-generations
nix-env --delete-generations 9 10 11
nix-collect-garbage -d
```

## Nixがなかったらどうなっていたか

Emacsで標準のパッケージマネージャはpackage.elであり、これはELPA互換のサーバからアーカイブをダウンロードする方式です。サーバがホストごとブロックされない限り、大企業のプロキシ環境でも使えるでしょう。

レジストリに登録されていないEmacs Lispパッケージをインストールしなければいけない場合、標準でないパッケージマネージャーや、Emacs 29のpackage-vc.elを使うことになります。 このような用途では以下のパッケージマネージャーが人気ですが、パッケージのソースをgit-clone（またはgit-submodule）でローカルにコピーする方式であるため、大企業のプロキシ環境では使えないでしょう。

-   [elpaca](https://github.com/progfolio/elpaca)
-   [straight.el](https://github.com/radian-software/straight.el)
-   package-vc (Emacs 29)
-   [borg](https://github.com/emacscollective/borg)

IDEのような本格的な環境がデフォルトで使えることで人気の[Doom Emacs](https://github.com/doomemacs/doomemacs)も、[straight.elを使っています](https://github.com/doomemacs/doomemacs/blob/master/docs/getting_started.org#package-management)。 やはり今回のプロキシ環境ではパッケージをインストールすることができず、使えないでしょう。

Twistは、プロキシ環境下でパッケージを更新することはできないものの、スナップショットをインストールすることはできますし、hackも不要です。

ちなみに、そのほかにNixを使った方法として、[NixOSのマニュアルに記載されている方法](https://nixos.org/manual/nixos/stable/#module-services-emacs-adding-packages)や、[emacs-overlayが提供している関数](https://github.com/nix-community/emacs-overlay#extra-library-functionality)があります。 この方法でも、同じように​`nix-store`​のexport/importで依存関係を含めてインストールすることはできるはずです。 git-cloneをしなくて済むのは、TwistというよりはNixの強みです。




## Emacsは役に立つのか？AIと形式手法が普及する今後の世界の中で

そもそも今までこういう客先常駐の現場で、Emacsパッケージをインストールできないことが問題にならなかったのは、必要なかったからだという見方もできるでしょう。 私がSESで最初に入ったのは、Springの案件でJavaのIDEとしてEclipseが使われていました。 Emacsの使用は許可されませんでしたが、私は割り当てられた業務をスキル面で問題なく遂行することができました。

次の現場も、同じようにEclipseが使われているSpringの案件でした。 私は配属された最初の月に、半年以上前にエントリレベルのメンバーが解決することができなかった、緊急度が高いバグの調査を割り当てられました。 私はWSL 1上のEmacsとEclipseを組み合わせてソースコードを調査することで、テストを含めて数時間で解決できました（もはやあまり憶えていませんが）。 Emacsのおかげに違いありません。 Emacsが使える人材は、ロースキル人材しかいない多重請負客先常駐の現場で役に立つ場合があるということはいえるでしょう。

とはいえ、こんなやり方はいい加減にやめたほうがいいのではとも思います。 今年に入ってから、世間を騒がせるシステム障害が多数発生しました。 ロースキル人材を使って、属人化を避けるために技術レベルを低く抑えながら開発を進めていく（？）SIerのやり方は、もう限界でしょう。 賢くてコミュニケーション能力が高い人たちが集まって話し合っても、間違いを犯すときはあります。 自然言語の曖昧さ・弱さを補うために、数学が必要でしょう。

https://twitter.com/Kory__3/status/1732798711343030379

https://x.com/kmizu/status/1917091479434235960

今後のプログラミングは、以下のリンク一覧で示されているように、AIと形式手法を組み合わせていく方法が主流になっていくはずです。

-   [Lean4 helped Terence Tao discover a small bug in his recent paper │ Hacker News](https://news.ycombinator.com/item?id=38035672)
-   [My team at AWS has been using P heavily for modelling and reasoning about distri&#x2026; │ Hacker News](https://news.ycombinator.com/item?id=34279169&s=09)
-   [形式手法でデータ構造を記述・検査してみよう：Alloy編 - DeNA Testing Blog](https://swet.dena.com/entry/2020/09/23/120000)
-   [(解説) はてなブックマークにおけるアクセス制御 - 半環構造に基づくモデル化](https://developer.hatenastaff.com/entry/bookmark-semiring-ac)
-   [Why Dont People Use Formal Methods?](https://www.hillelwayne.com/post/why-dont-people-use-formal-methods/)
-   [証明支援系がダメだった理由と、AIでブーストする理由 - 檜山正幸のキマイラ飼育記](https://m-hiyama.hatenablog.com/entry/2023/03/25/125509)
-   [make real, the story so far - by Steve Ruiz - tldraw](https://tldraw.substack.com/p/make-real-the-story-so-far)

https://x.com/hatsugai/status/1926326087996334377

そのような世界で、Language Server Protocol（LSP）やAIを統合するインタフェースとして、VS Codeが今後も伸び続けるのか、はたまた[Unit](https://github.com/samuelmtimbo/unit)のようなvisual programming環境が勃興していくのか。 その中でEmacsは、古き良きテキストベースのcomputing環境として、customizabilityと過去資産の強みを活かしながら生き残っていくのか。

私自身は、2019年の[Roam](https://roamresearch.com/)から始まってObsidianやLogseqなどにつながり、Notionにも影響を与えたZettelkastenブームのときに、しばらくブラウザベースのUIを試して、しかし操作効率のためにEmacsのorg-modeに帰りました。 [Elixir Livebook](https://livebook.dev/)もまた、使っていて楽しいソフトウェアではありますが、ソースコードの編集が辛く、時間が溶けやすいです（[Emacs keybindings](https://news.livebook.dev/vim-and-emacs-key-bindings---launch-week-2---day-5-VfcA0)はEmacsではありません）。 Emacsの代わりはありません。

今後のトレンドがどう変わるにせよEmacsはなくならないでしょうし、Emacsよりも客先常駐のほうが先になくなるのではないでしょうか。 AIを制限せずにエディタを制限するような倒錯した状況が起きることはあってほしくありません。

## 車輪の再発明をせず、Nixのエコシステムに乗っかることの有用性

"Don't reinvent the wheel." IT関係者（一緒にするなと言われそうですが）の間ではよく引用されるこの言葉、日本語では「車輪の再発明（をするな）」と訳されます。

私は[twist.nix](https://github.com/emacs-twist/twist.nix)を書く前、Emacsのパッケージマネージャーとして人気だった[straight.el](https://github.com/radian-software/straight.el)を使っていました。 当時のstraight.elにはいくつかのバグがあり（もはや何のバグだったかも憶えていませんが）、このパッケージマネージャーは正しいことをしているのだろうか？と私は疑問を抱きました。

straight.elは（もはや当時とは説明が若干異なるかもしれませんが)次のように謳っています。

> Next-generation, purely functional package manager for the Emacs hacker.

purely functionalといえばHaskellです。 Haskellは、プログラマが型システムの助けを借りながらリライトを繰り返すことで、正しい問題理解に到達することができる言語であることが知られています。 このような体験が実現できるのは、強い静的な型システムを備えた純粋関数型言語という特徴のおかげです。

依存性解決をしながらたくさんのリポジトリをgit-cloneするのも、パッケージをアップグレードするのも、正しく実装するのは簡単ではないでしょう。 straight.elは、実行時にアプリケーションとして間違った状態に入ってしまう場合があるのでは？と思いました。 動的型付けの不純な言語であるEmacs Lispには、アプリケーションが間違った状態に入らないことを保証する仕組みがありません。

Emacsを使う人はすべてのタスクをEmacsの中で遂行したいと思うものです。 Emacsのパッケージマネージャが壊れたら、何もできなくなるということでもあります。 私だったら、Emacs Lispでパッケージマネージャーを実装しようとは思いません。

Emacs Lispのパッケージを正しくビルドするために、パッケージをビルドする部分をNixに押し付けることにしたのが、twistの出発点の一つでした。 今回は、パッケージをビルドするプロセスの正しさというパッケージマネージャーの本質的な部分ではなく、ビルドされた結果をexport/importするというNixの追加機能の部分に助けられました。 これもまた、資本の乏しい個人が独自のエコシステムを構築しようとはせずに、Nixのエコシステムに乗っかったからです。

## Nixに入門するには

Nix/NixOSに入門したい人におすすめのリソースは、[Xeさんのブログ](https://xeiaso.net/blog/)です。 Nixに限らず、tech-savvyになりたい人には参考になる記事がたくさんあると思います。

1.  [Nix Flakes: an Introduction - Xe Iaso](https://xeiaso.net//blog/nix-flakes-1-2022-02-21/)
2.  [Nix Flakes: Packages and How to Use Them - Xe Iaso](https://xeiaso.net//blog/nix-flakes-2-2022-02-27/)
3.  [Nix Flakes: Exposing and using NixOS Modules - Xe Iaso](https://xeiaso.net//blog/nix-flakes-3-2022-04-07/)

[nix.dev](https://nix.dev/)のコンテンツにも目を通しておくとよいでしょう。nix.devには随時新しい内容が追加されており、私もすべての内容は把握していませんが、公式かつ実際的な内容を網羅しており、品質面で信頼できます。

日本国内では、[nix-ja](https://github.com/nix-ja)というコミュニティがあり、Discordのグループで情報交換をしているようです（私自身は不参加のため内容未確認）。 Asahi氏の次の記事は情報がまとまっており、参考にするといいでしょう。

https://zenn.dev/asa1984/articles/nixos-is-the-best

## まとめ

本記事のまとめです。

-   大企業のプロキシ環境では、特定のサイトをブロックしているだけでなく、プロトコル単位での制限もあり、外部のpublicなリポジトリをgit-cloneすることができない場合がある。このような環境では、asdfなどの一部のパッケージ管理ツールを利用することができない。
-   Nixパッケージマネージャでは、​`nix-store`​を使ってパッケージを依存関係と一緒にdumpすることで、オフラインの環境にもパッケージを持ち込んでインストールすることができる。このパッケージは、プラットフォーム（OS・CPUアーキテクチャ）に依存するが、ディストリビューションには依存せず、NixOS以外のLinux環境でも動作する。
-   Nixは、パッケージを正しくビルドするだけでなく、Nix storeのexport/importなどといった管理機能においても優れている。車輪の再発明を始める前にNixの導入を検討するとよい。