【解決】Julia でパッケージの更新がsshのエラーで失敗【大幅加筆修正】

# TL;DR

```bash:~/.ssh/chk_and_run_ssh_agent.sh
# Auto Start and/or Setup `ssh-agent`
# Usage (in `.bashrc`): `source $HOME/.ssh/chk_and_run_ssh_agent.sh`
if [ -z "$SSH_AUTH_SOCK" ]; then
   # Check for a currently running instance of the agent
   RUNNING_AGENT="$(ps -ax | grep 'ssh-agent -s' | grep -v grep | wc -l | tr -d '[:space:]')"
   if [ "$RUNNING_AGENT" = "0" ]; then
      # Launch a new instance of the agent
      ssh-agent -s &> $HOME/.ssh/ssh-agent
   fi
   eval $(cat $HOME/.ssh/ssh-agent)
fi
```

【2020/09/17 追記・更新】
割と根本的な解決っぽいものを見つけたので大幅更新。

# はじめに

これたぶん普通の使い方をしている人なら巡り逢わないトラブルじゃないかな、、、。
でも **テレワーク（リモートワーク）が当たり前になりつつある昨今** 、もしかしたら今後あなたも、このエラーに遭遇することがあるかもしれません。

ということでまずは、どのようなエラーが起きうるのか、それを示します。

[Julia](https://julialang.org) である特殊な条件下で、パッケージの更新（＝Pkg REPL-mode で `up` を実行）しようとしたとき、以下のようなエラーが出て失敗することがあります[^v1x]（パス名やパッケージ名等は仮のものです）：

```jl
$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.5.1 (2020-08-25)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

(@v1.5) pkg> up
   Updating registry at `~/.julia/registries/General`
   Updating git-repo `git@github.com:JuliaRegistries/General.git`
Private key location for 'git@github.com': ~/.ssh/id_xxx_rsa
Private key location for 'git@github.com' [/path/to/userhome/.ssh/id_xxx_rsa]: 
Private key location for 'git@github.com' [/path/to/userhome/.ssh/id_xxx_rsa]: 
┌ Warning: Some registries failed to update:
│     — /path/to/userhome/.julia/registries/General — failed to fetch from repo
└ @ Pkg.Types /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Pkg/src/Types.jl:1122
   Updating git-repo `https://github.com/someuser/OreorePkg.jl.git`
Private key location for 'git@github.com': ~/.ssh/id_xxx_rsa
Private key location for 'git@github.com' [/path/to/userhome/.ssh/id_xxx_rsa]: 
Private key location for 'git@github.com' [/path/to/userhome/.ssh/id_xxx_rsa]: 
ERROR: failed to fetch from https://github.com/someuser/OreorePkg.jl.git, error: GitError(Code:EAUTH, Class:Callback, Aborting, maximum number of prompts reached.)

```

[^v1x]: Julia v1.5.1 での動作例を示しますが、Julia v1.x 全て同様です。

起きていることを説明するとこんな感じ：

+ パッケージを更新しようとしたら、ssh の private key[^rsa_key] の在処を聞かれた。
+ ↑で正しいパス（この例だと `~/.ssh/id_xxx_rsa`）を入力しても繰り返し聞かれた。
+ 挙げ句「更新に失敗した」「fetch 出来なかった」という警告やエラーが出て更新できなかった。

[^rsa_key]: github.com に SSH Key を登録したときの、登録した public key じゃない方。「github.com に SSH Key を登録」の意味がわからない方はたぶんこの記事は意味を持たないと思います。

## 環境

+ Linux/xxxux
    + Ubuntu 19.04 で動作確認
+ シェル：Bash
    + 他でも同様

# 解決方法1

まずは冒頭に示したファイルを用意します（念のため↓に再掲）。

```bash:~/.ssh/chk_and_run_ssh_agent.sh
# Auto Start and/or Setup `ssh-agent`
# Usage (in `.bashrc`): `source $HOME/.ssh/chk_and_run_ssh_agent.sh`
if [ -z "$SSH_AUTH_SOCK" ]; then
   # Check for a currently running instance of the agent
   RUNNING_AGENT="$(ps -ax | grep 'ssh-agent -s' | grep -v grep | wc -l | tr -d '[:space:]')"
   if [ "$RUNNING_AGENT" = "0" ]; then
      # Launch a new instance of the agent
      ssh-agent -s &> $HOME/.ssh/ssh-agent
   fi
   eval $(cat $HOME/.ssh/ssh-agent)
fi
```

2行目のコメントに書いた要領で、以下を `.bashrc` に追記します[^bashrc]。他のログインシェルを利用している方は適宜読み替えたり上記ファイルも書き換えたりしてください。

```bash:~/.bashrc（抜粋）
# : 以下を末尾に追記
source $HOME/.ssh/chk_and_run_ssh_agent.sh
```

[^bashrc]: `chk_and_run_ssh_agent.sh` の内容をそのまま `.bashrc` に追記してももちろん構いません。あと [参考文献](https://code.visualstudio.com/docs/remote/troubleshooting#_setting-up-the-ssh-agent) では「`.bash_profile` に追記する」とありますが、私が調査・実験した限りは `.bashrc` の方がより適切に動作する模様です。

この状態でログインすると、以下のファイルが（存在しなければ自動で）作成されます（パス名・PID値等は仮です）。

```bash:~/.ssh/ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-xxxxxxxxxxxx/agent.54320; export SSH_AUTH_SOCK;
SSH_AGENT_PID=54321; export SSH_AGENT_PID;
echo Agent pid 54321;
```

2つの環境変数が設定されていますが、最初の `SSH_AUTH_SOCK` がポイント。これによって **ssh-agent** が正常に動いていてそれにアクセスする方法が提供されます[^SSH_AGENT_PID]。

[^SSH_AGENT_PID]: `SSH_AGENT_PID` の方は読んで字の如く、今動いている `ssh-agent` のPIDです（それ以上の情報はありません（たぶん））。

適切に設定できれば、そこにログインすれば普通にエラー無くJuliaでパッケージ更新できます。

実行例：

![JuliaUp_2020-09-17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30400/78f49479-b428-6422-9505-6364f44908c0.png)

## Julia側の追加対応

上記の通り、これでもう問題なく動くはずですが、場合によってはこれでも Julia でパッケージ更新しようとしたときに依然として同じエラーが出る可能性があります。
Julia 起動時に何らかの原因で `SSH_AUTH_SOCK` 環境変数が設定されなかった場合がそれに該当します。
でも環境変数が設定されていない状況で一度でもログインが出来ていれば、ssh-agent が起動しその情報を記したファイルが `~/.ssh/ssh-agent` にはできているはず。
なので、`~/.julia/config/startup.jl` に以下の内容を記述（ファイルが存在しなければ作成）すればOK。

```julia:~/.julia/config/startup.jl（抜粋）
# :
ssh_agent_path = joinpath(homedir(), ".ssh", "ssh-agent")
if isfile(ssh_agent_path) && !haskey(ENV, "SSH_AUTH_SOCK")
    open(ssh_agent_path, "r") do f
        for line in eachline(f)
            for sentence in split(line, ';')
                if occursin('=', sentence)
                    key, value = strip.(split(sentence, '='))
                    ENV[key] = value
                end
            end
        end
    end
end
# :
```

# 解決方法2

【2020/09/17 補足追記】
以降は解決方法1を見つける以前の方法です。
暫定的な対処としては依然として有効なので、残しておきます。

このエラーに遭遇したら、パッケージ更新前にシェルで以下のような一連のコマンドを実行すればOKです：

```bash
eval $(ssh-agent) > /dev/null
ssh-add ~/.ssh/id_xxx_rsa
```

`~/.ssh/id_xxx_rsa` の部分は先程例で示した private key のパスです。
このあと Julia の Pkg REPL で `up` すれば何事もなかったようにエラー無く更新できます。
実行例[^shellmode]：

```bash
$ eval $(ssh-agent) > /dev/null
$ ssh-add -l  # 登録されていないと↓のように表示される
The agent has no identities.
$ ssh-add ~/.ssh/id_xxx_rsa
Identity added: /path/to/userhome/.ssh/id_xxx_rsa (username@hostname)
$ ssh-add -l  # 正常に登録されたら↓のように表示される
2048 SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX username@hostname (RSA)
$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.4.0 (2020-03-21)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

(@v1.4) pkg> up
   Updating registry at `~/.julia/registries/General`
   Updating git-repo `git@github.com:JuliaRegistries/General.git`
   Updating git-repo `https://github.com/someuser/OreorePkg.jl.git`
   Updating `~/.julia/environments/v1.4/Project.toml`
 [no changes]
   Updating `~/.julia/environments/v1.4/Manifest.toml`
 [no changes]

(@v1.4) pkg> 

```

[^shellmode]: julia コマンド実行前にシェル上で実行することが重要。なぜなら先述の通り「必要な環境変数が設定されること（された状態でパッケージの更新を行うこと）」が重要なので。記事修正前に *「Julia の shell mode（REPLで <kbd>;</kbd>（セミコロン）キーを押す）で実行してもOK」* と書いてましたがウソでした💦

更新時に private key location を聞かれることもなくなり、何の問題もなく更新できました！

# 解説

そもそもどういう状況のときになんでこんなエラーが発生するのか。
もし Julia が正しくセッティングされていれば、そもそもパッケージ更新時に ssh key を聞かれることすらありません。

このようなことが起きる条件・原因は、簡単にまとめると以下の通り：

+ ssh 等でサーバや他のPCやVirtualBox等の上で動いているLinux（特に xubuntu 等 X Window System が動作している環境）に接続し、そこで Julia を実行している（※1）。
+ Julia のパッケージ（またはパッケージレジストリ）が `https://github.com/〜` ではなく `git@github.com:〜` という ssh url でアクセスするよう設定されている（※2）。
+ `ssh-agent` が起動していない、もしくは github にアクセスするための ssh private key が `ssh-add` で登録されていない。

※1 は、例えば [Visual Studio Code](https://code.visualstudio.com/) の [Remote Development](https://code.visualstudio.com/docs/remote/remote-overview) プラグインで ssh 接続した先の Julia コードを開発・編集しようとしているときも当てはまります。

※2 がキーポイント。これは細かく言うとさらにいくつかのパターンがある[^ssh_pattern]のですが、共通して言えることとして、以下の設定をした覚えがあるはずです：

+ `ssh-keygen` コマンドで SSH Key を生成し、public key を github.com に登録した
+ `~/.ssh/config` に以下のような設定を記述した（多少の違いはあれど）：

```config:.ssh/config(抜粋)
Host github github.com
  HostName github.com
  IdentityFile ~/.ssh/id_xxx_rsa
  User git
```

[^ssh_pattern]: 例えば、「1. オレオレパッケージ（公式パッケージレジストリに登録されていないパッケージ）を追加するときに、https ではなく `]add git@github.com:〜` のように ssh url で追加した場合」、「2. git で github.com にアクセスするときに _常に_ ssh url を使うよう設定されている場合（`~/.gitconfig` で `[url "git@github.com:"]` エントリを設定した場合）」など

この設定自体には問題がないのですが、Julia（の `Pkg` モード）が github.com とやり取りする際、実はこのファイルを（直接は）参照していません。
`git` コマンドを実行しているわけではなく LibGit2 というライブラリを介して Git 周りの処理をしているのですが、それが `~/.ssh/config` を見ておらず、[ssh-agent](https://www.ssh.com/ssh/agent) というヘルパープログラムと鍵のやり取りをしているの（だそう）です。
ところが、X Window system の動いている Linux 環境に（ssh 等で）ログインした場合、そのシェル環境では ssh-agent が正常に動作していない、もしくは適切な private key が登録されていない状態になるのだそうです[^x_ssh_agant]。
それが原因となって、結果として Julia のパッケージの更新が失敗する、ということだそうです（ややこしい）。

[^x_ssh_agant]: 少し調べたところ、GUIでログインした場合には `/etc/X11/Xsession.d/90x11-common_ssh-agent` という初期化スクリプトが動いて適切な設定がされる（ので問題なく使える）ようです。そうでない場合はこれが動かず、他に適切に設定するスクリプトがない、ということのようです。

ので、ssh-agent を適切に（自動）起動（させて `ssh-add` コマンドで適切な private key を登録[^ssh_add]）すれば動くようになる、ということです[^everytime]。

[^ssh_add]: 解決方法1では明示的に `ssh-add` コマンドを実行していません。本来はこれでOKで、gitクライアントがssh-agentにプライベートキーを要求したときに登録されていなければ自動で登録される仕組みになっています（はず）。

[^everytime]: 修正前に書いていた以下の脚注、自己解決しました（本記事の前半参照）→ ~~ただ毎回これを実行するのはやや面倒ですよね…うまいこと自動化する方法があるんでしょうかね？ セキュリティ的にも安心で良い自動化の方法があったらどなたか教えてください:bow_tone1:~~

## 別解

一応、もう1つ解決方法があります。
**git で ssh の利用をやめる**、という方法です。

そもそも github.com では実は [ssh よりも https の利用を推奨](https://help.github.com/en/github/using-git/which-remote-url-should-i-use) しています。
何らかの理由で「https プロトコルで git を利用できない（したくない）」ということでもなければ、ひょっとしたらその方が幸せになるかもです。

「リポジトリの remote url が ssh になっていたら https に変更する」「個人アクセストークンを取得する（オプション）」「`.gitconfig` の設定を（再）編集する」等の作業は必要になってきます。
この記事の範囲を超えるので（実験もしてないので）興味のある方はご自分で試してみてください。

# 参考

+ [ssh-agent](https://www.ssh.com/ssh/agent)
+ [post #6 - Pkg.clone keeps asking for my SSH key](https://discourse.julialang.org/t/pkg-clone-keeps-asking-for-my-ssh-key/15155/6)
+ [Creating a personal access token for the command line - GitHub Help](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line)
+ [Setting up the SSH Agent - Visual Studio Code Remote Development Troubleshooting Tips and Tricks](https://code.visualstudio.com/docs/remote/troubleshooting#_setting-up-the-ssh-agent)