以下の内容はhttps://turgenev.hatenablog.com/entry/2026/02/11/121330より取得しました。


【Android】proot無しのTermuxでVS Code Remote SSHを使う

概要

Androidで動く一番軽い部類のLinuxであるTermuxの上で、Visual Studio CodeのRemote SSHを動かそうという記事です。

TermuxでVS Code Remote SSHを使うとなると、もう少しまともな(そのかわり重い)Linux技術であるprootを使うのが普通で、調べてもそれしか出てこないのですが、頑張ればproot無しのTermuxでも動きました。

ブラウザで動くcoder/code-serverと違って、プロセス起動を伴うような拡張機能も(全てかは分かりませんが)動作します。

glibcとbionic libc、3つの環境変数

まず、VS Code 1.99以降(2025年3月頃~)のLinuxでの動作要件には、以下のようなものが含まれています。

glibc >=2.28, libstdc++ >= 3.4.25

これは、VS Codeが使用するNode.jsの本体やバイナリモジュールの実行に必要なものです。

一方で、たとえばUbuntu 18.04だとglibcバージョンは2.27だったりするなど、未だに使われているLinuxの中にもこの要件を満たせないものは多くあります。

そこで、1.99からは、以下の3つの環境変数が導入され、これによってもともと要件を満たすglibcが入っていない環境でも、自分でglibc関連のファイルを用意すれば(参考例)、(一応サポートされていないという警告は出ますが)VS Code Remote SSHが動かせるようになりました。

  • VSCODE_SERVER_CUSTOM_GLIBC_LINKER
  • VSCODE_SERVER_CUSTOM_GLIBC_PATH
  • VSCODE_SERVER_PATCHELF_PATH

glibcについては、https://github.com/hsfzxjy/vscode-remote-glibc-patchのように有志がビルドしてくれているものを使うことも可能です。

一方でTermuxは、別に古い環境というわけではないのですが、そもそもglibcではなくbionic libcという、GoogleがAndroid用に開発した特殊なlibc環境で動いています。

従ってglibc用にコンパイルされたプログラムは動かないですし、上記のVS Codeの動作要件も満たしません。やはり自分でglibcを導入する必要があります。

glibc導入

実はTermuxにおいては、ビルド済みのglibcがパッケージとして配布されています。

pkg install glibc-repoをしたあとにpkg install glibcとすると入れられます。合計400-500Mくらいあって、多分削れるものもあるんでしょうが、考えるのが面倒なので入れます。一緒に入るglibc-runnerというパッケージにはgrunというコマンドがあり、これはglibc用のバイナリをいい感じに実行してくれるラッパー的なもので、動作確認に便利です。

導入すると、$PREFIX/glibc/以下にbin/とかlib/とかが作られてglibc関連のファイルがたくさん追加されます。

環境変数の設定

sshでログインできる状態にはなっているとして、とりあえずVS Code Remote SSHで接続してみると、以下のようなエラーが出ると思います。

リモート ホストは、glibc および libstdc++ の VS Code Server の前提条件を満たしていない可能性があります (リモート ホストは、VS Code Server を実行するための前提条件を満たしていません)   

glibcが入っていてもさきほどの3つの環境変数が設定されていないためです。これをどうにかする必要があります。

VS CodeがRemote SSHをするときはssh -T -D 54321 example.com sh(54321のところはランダムなポート番号)のようなコマンドラインで接続してきます。これに対して環境変数を設定することになりますが、いくつかの選択肢があります。

  • authorized_keysでcommandを指定する…基本的には一番まともな方法だと思います。ただし副作用として、通常ログイン(コマンドを指定せずssh example.comだけするとき)で見えるはずのウェルカム的なメッセージ(motd)が見えなくなります。どうせTermuxが相手なのでそれでも困らないとは思いますが、気になるのであれば鍵を分けるとよいです。
  • クライアント側のVS CodeのSSHの実行ファイルパス(remote.SSH.path)で独自のラッパーなどを指定し、コマンドライン引数をいじる…サーバー側ではなくクライアント側で解決する方法です。ただプロファイルを分けない限り他のサーバーにも影響するのであまり嬉しくないとは思います。
  • .bashrcや.profileのようなファイルで設定する…上記のshコマンドはログインシェルではないので.profileや.bash_profileは読んでくれません。.bashrcについては、他のLinuxでは読んでくれる場合もありますが、Termuxでは($PREFIX/bin/shをdashではなくbashへのシンボリックリンクにした場合でさえ)読んでくれないようです。他に方法があるかもしれませんが、知る限りこの方法はダメそうです。

ここでは一番上の方法を採用します。

具体的には、例えば以下のようなシェルスクリプトを$HOME/ssh-login.shとして作成します。

#!/bin/sh
if [ -z "$1" ]; then
exec $SHELL -l
else
if [ ! -t 0 ] && [ "$1" = "sh" ] ; then #VS Code
  export VSCODE_SERVER_CUSTOM_GLIBC_LINKER='/data/data/com.termux/files/usr/glibc/lib/ld-linux-aarch64.so.1'
  export VSCODE_SERVER_CUSTOM_GLIBC_PATH='/data/data/com.termux/files/usr/glibc/lib/'
  export VSCODE_SERVER_PATCHELF_PATH='/data/data/com.termux/files/usr/glibc/bin/patchelf'
export XDG_RUNTIME_DIR=$TMPDIR export BASH_ENV=$HOME/.vscode-server/vscode-sleep exec bash fi eval "$1" exit

そして、authorized_keysで以下のようにします。

command="$HOME/ssh-login.sh \"$SSH_ORIGINAL_COMMAND\"" ssh-ed25519 AAAAC3NzaC1lZ............

これにより、$SSH_ORIGINAL_COMMANDが$1として渡されてきます。これが空文字列のときは引数無しでの通常ログインなのでログインシェルを実行し(前述の通りmotdは出なくなる)、そうでない場合は与えられたコマンドをそのまま実行するが、shかつターミナル割り当て無しのときはVS Codeを想定して環境変数を設定する、というロジックになっています。

ちなみにこの方法はVS Code以外でも結構使える方法だと思います。

XDG_RUNTIME_DIR=$TMPDIRというのは、VS Codeが使う一時フォルダとしてまずXDG_RUNTIME_DIRを見て、無かったら/tmpを使うロジックになっていて、/tmpが読み書き不可でXDG_RUNTIME_DIRもないTermuxでは不十分だからです。無くても接続はできますが、ssh-agent関連(後述)で失敗します。

BASH_ENVとexec bashについては後述します。

ここまでで、さっきのエラーがとりあえず出なくなります。

ダウンロードで無限ループする問題

この状態で接続してみると、「VS Code サーバーをダウンロードしています…」というのが出て、ダウンロードが進行しているのが見えるのですが、ダウンロードと展開が終わったところで、接続が成功せず、また最初からダウンロードが始まって無限ループしてしまいます。

明確なエラーが出ないので結構デバッグが難しいのですが、結論からいうと、これは.vscode-server/cli/servers/Stable-xxxxxx/server/bin/code-serverのshebang(1行目)が#!/usr/bin/env shとなっていて、Termuxには/usr/binがないので実行できない、というのが原因です。(エラーメッセージが出るとしたら、/usr/bin/env: bad interpreter: No such file or directoryのような感じ)

Termuxでは、これに対処するため、LD_PRELOADに$PREFIX/lib/libtermux-exec-ld-preload.soが設定されているのですが、VS Code側の.vscode-server/code-xxxxxxはlibcではなくmuslの静的リンクのバイナリなので効きません。

そこで、termux-fix-shebangという、Termuxで実行できる形にshebangを書き換えてくれるコマンドを使う必要があります。

termux-fix-shebangをする方法①(追記)

  • 最初に書いていた方法が不安定だったのでこちらを追記しました。最初の内容も次節に残してあります。

VS Codeのサーバー側ファイルの展開においては、.vscode-server/cli/servers/Stable-xxxxxx.stagingというようなフォルダに展開されてから.stagingが外れる(リネームされる)動作になっています。

そこで、確実にtermux-fix-shebangを実行できるように、先回りして.stagingがないほうのフォルダを作成してしまい、偽物のcode-serverを配置しておきます。

この偽物のcode-serverが、自分自身を削除した上で、本物のcode-serverを配置し、termux-fix-shebangを実行し、本物のcode-serverを実行する、という流れになります。また、この際ついでにserver/bin/remote-cli/code(VS Code Remote SSHの統合ターミナルで使えるcodeコマンド)のほうもtermux-fix-shebangします。

.stagingのmvに関するエラーは出ますが、これでうまくいきます。

まず偽物のcode-serverの中身です。

#!/bin/sh
#remove myself and run the real code-server after fix-shebang
cd $(dirname $0)/../..
ROOT=$(pwd)
rm -rf $ROOT/server mv $ROOT.staging/server $ROOT/server rm -rf $ROOT.staging termux-fix-shebang $ROOT/server/bin/code-server
sed -i '1a unset LD_PRELOAD' "$ROOT/server/bin/code-server"
termux-fix-shebang $ROOT/server/bin/remote-cli/code
sed -i '1a unset LD_PRELOAD' "$ROOT/server/bin/remote-cli/code" exec $ROOT/server/bin/code-server "$@"

これをvs-code-server-initという名前で保存します。最後のほうのsedについては後述します。

それと同じフォルダに以下のようなスクリプトを作成します。xxxxxx.stagingという名前のフォルダができたら上記のスクリプトをbin/code-serverに配置するだけです。

#!/bin/sh
cd $(dirname $0)
INIT_SCRIPT=$(pwd)/vs-code-server-init
BASE_DIR="$HOME/.vscode-server/cli/servers"
mkdir -p "$BASE_DIR"
echo "Monitoring $BASE_DIR..."
cd $BASE_DIR
while true; do
    TARGET_DIR=$(find "$BASE_DIR" -maxdepth 1 -type d \( -name "*.staging" \) | head -n 1)
    if [ -n "$TARGET_DIR" ]; then
        echo "Target directory found: $TARGET_DIR"
        TARGET_DIR2=$(echo $TARGET_DIR | sed "s/\.staging$//")
        mkdir -p $TARGET_DIR2/server/bin
        cp $INIT_SCRIPT $TARGET_DIR2/server/bin/code-server
        exit
    fi
    sleep 1
done

ここまでの設定がうまくできていれば、無事にサーバーへの接続が成功するはずです。

一旦ダウンロードが済めばこのスクリプトは必要ありません。ただしバージョンアップ時にはまた必要になります。アップデートの頻度は多分1か月に1回とかなのでそんなに困らないと思います。どうせTermuxなんてメインで使わないですし。

termux-fix-shebangをする方法②

  • 最初に書いていたのはこちらの方法です。しかしアーカイブの展開順によってはかなりタイミングがシビアになるため、やや不安定に感じます。

アーカイブの展開が終わってからcode-serverが実行されるまでのわずかな時間の間にtermux-fix-shebangを突っ込みます。

#!/bin/sh
BASE_DIR="$HOME/.vscode-server/cli/servers"
mkdir -p "$BASE_DIR"
echo "Monitoring $BASE_DIR..."
cd $BASE_DIR
while true; do
    TARGET_DIR=$(find "$BASE_DIR" -maxdepth 1 -type d \( -name "*.staging" \) | head -n 1)

    if [ -n "$TARGET_DIR" ]; then
        TARGET_FILE="$TARGET_DIR/server/bin/code-server"
        echo "Target directory found: $TARGET_DIR"
        
        while true; do
            if [ -f "$TARGET_FILE" ]; then
                echo "File detected. Starting repetitive patch loop..."
                
                while [ -f "$TARGET_FILE" ]; do
                    termux-fix-shebang "$TARGET_FILE"
                    sed -i '1a unset LD_PRELOAD' "$TARGET_FILE"
# sleep 0.01 done # fix `code` sleep 0.3 TARGET_DIR2=$(echo $TARGET_DIR | sed "s/\.staging$//") termux-fix-shebang "$TARGET_DIR2/server/bin/remote-cli/code" sed -i '1a unset LD_PRELOAD' "$TARGET_DIR2/server/bin/remote-cli/code" exit 0 fi # resume if directory is disappeared if [ ! -d "$TARGET_DIR" ]; then break fi # sleep 0.01 done fi sleep 1 done

patchelf・nodeが動かない問題

先ほどのスクリプトでは、code-serverの中でLD_PRELOADをunsetするように変えました。この理由は、デフォルトでLD_PRELOADに設定されている$PREFIX/lib/libtermux-exec-ld-preload.soが、glibcではなくbionic libcのバイナリであり、glibc系のバイナリ(たとえば$PREFIX/glibc/bin/patchelfや、それを使って実際にパッチされたバイナリ)と一緒に実行すると

error while loading shared libraries: /data/data/com.termux/files/usr/glibc/lib/libc.so: invalid ELF header

というようなエラーが出てしまうからです。(結果的にはこれもダウンロードループになります)

glibc-runnerに入っているgrunコマンドはこのあたりも上手くやってくれます。が、VS Code Remote SSHを動かすにあたってはgrunを使うよりLD_PRELOADをunsetする方法のほうがうまく行くようです。

署名の検証ができない

一通り問題なく動作はしているのですが、拡張機能を入れようとすると、「Signature verification failed with 'ENOENT' error」というエラーが出ます。これは、署名の検証に使用される

~/.vscode-server/cli/servers/Stable-xxxxxx/server/node_modules/@vscode/vsce-sign/bin/vsce-sign

というファイルがglibc依存で、依存ライブラリがなくて実行できないからです。

それならばと、grunなどを使ってglibcで実行しようとしても、possible file corruptionとかいうエラーが出てしまいます。このvsce-signはクローズドソースなので、自分でビルドすることもできません。

HTTPSのおかげで、そう簡単に悪意のあるバイナリに置き換えられることはないと思うので、さしあたっては、警告を無視して拡張をインストールするのでも良さそうです。あるいは、vsce-signを

#!/bin/sh
true

のような必ず成功する実行ファイルに置き換えれば、警告は出なくなります。

設定でExtensions: Verify Signatureのチェックを外すこともできますが、リモート先を限定して設定することはできないのであまり望ましくないです。

きちんと検証したい場合は、一応、https://github.com/filiptronicek/node-ovsx-signに非公式な互換実装があるようです。あるいは自前でサーバーを立ててネットワーク経由で検証するという選択肢もあります。

統合ターミナルでのLD_PRELOAD

unset LD_PRELOADをした状態でリモート側サーバーが起動しているので、統合ターミナルでもその状態になります。これにより、普通のTermuxのシェルで問題なく実行できるシェルスクリプトが統合ターミナルでは実行できないという事態が発生します。

VS Codeの内側である統合ターミナルではLD_PRELOADを再び設定しておいても実行に支障はないはずなので、

#!/bin/sh
export LD_PRELOAD=$PREFIX/lib/libtermux-exec-ld-preload.so
exec bash

というスクリプトをmybashという名前で作成して、

terminal.integrated.profiles.linux(リモート先ごとに設定できます)を編集して、

    "mybash": {
      "path": "/path/to/mybash"
    }

を追加しておけばいいです。

ただしcodeコマンドではnodeが内部で実行されるので再びunsetする必要があります。先ほどsedでcodeについてもunsetの行を入れていたのはこのためです。

拡張機能の動作状況

拡張機能については、glibcに依存しないものであれば問題なく動くと思います。例えば、多くの場合、Goのバイナリはlibc非依存ですが、Rustはlibc依存です。従って、(Goで実装されている)Goの拡張機能(golang.go)は基本的に問題なく動きますが、(Rustで実装されている)Rustの拡張機能(rust-lang.rust-analyzer)は、rust-analyzerのパスを変更してgrunを挟むようにしないと動きませんでした。

phantom process killing対策 ①sshをシングルプロセスに

ちょっと前の【Android】非rootなTermuxでTailscaleを動かす - turgenev’s blogで説明した通り、最近のAndroidにはphantom process killingというのがあり、32を超えるプロセスがあるとランダムにkillされます。

普通にVS Code Remote SSHを使っていると結構プロセス数を圧迫するので、いくつか削りたくなります。

まず削れそうな部分として、sshdのプロセスがあります。

sshで新たに接続すると、sshdの下にsshd-session -Rというのが2つできて、その下にようやくbashのようなメインのプロセスが来ます。

この2つのプロセスの目的はわかりませんが、すくなくとも単一ユーザーのTermuxであれば、プロセス1個だけで、公開鍵認証をしつつ適切なコマンドを起動して接続を受けることは可能なはずです。

実際、たとえばhttps://github.com/gliderlabs/sshがこれをやってくれます。以下のように書くことで、さきほどのauthorized_keysと同等の動作をさせることができます。見やすさのため、ssh-login.shにはパスが通っているものとしました。リモート・ローカル双方のポートフォワードも有効です(ローカルのほうは無いとVS Code Remote SSHが動きません)。

func ssh_main() {
	authKeyStr := "ssh-ed25519 AAAAC3NzaC1............... user@pc"
	pubKey, _, _, _, _ := ssh.ParseAuthorizedKey([]byte(authKeyStr))
	publicKeyOption := ssh.PublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
		return ssh.KeysEqual(key, pubKey)
	})
	forwardHandler := &ssh.ForwardedTCPHandler{}
	server := ssh.Server{
		Addr: "localhost:8522",
		Handler: func(s ssh.Session) {
			authorizedKey := gossh.MarshalAuthorizedKey(s.PublicKey())
			io.WriteString(s, fmt.Sprintf("public key used by %s:\n", s.User()))
			s.Write(authorizedKey)
			cmd := exec.Command("ssh-login.sh", s.RawCommand())
			done := make(chan struct{})
			defer close(done)
			go func() {
				<-s.Context().Done()
				if cmd.Process != nil {
					syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
				}
			}()
			ptyReq, winCh, isPty := s.Pty()
			if isPty {
				cmd.Env = append(os.Environ(), fmt.Sprintf("TERM=%s", ptyReq.Term))
				f, err := pty.Start(cmd)
				if err != nil {
					panic(err)
				}
				defer f.Close()
				go func() {
					for win := range winCh {
						pty.Setsize(f, &pty.Winsize{
							Rows: uint16(win.Height),
							Cols: uint16(win.Width),
						})
					}
				}()
				go func() {
					io.Copy(f, s) // stdin
				}()
				io.Copy(s, f) // stdout
				cmd.Wait()
			} else {
				cmd.Stdin = s
				cmd.Stdout = s
				cmd.Stderr = s.Stderr()
				cmd.Run()
			}
		},
		LocalPortForwardingCallback:   ssh.LocalPortForwardingCallback(func(ctx ssh.Context, dhost string, dport uint32) bool { return true }),
		ReversePortForwardingCallback: ssh.ReversePortForwardingCallback(func(ctx ssh.Context, host string, port uint32) bool { return true }),
		RequestHandlers: map[string]ssh.RequestHandler{
			"tcpip-forward":        forwardHandler.HandleSSHRequest,
			"cancel-tcpip-forward": forwardHandler.HandleSSHRequest,
		},
		ChannelHandlers: map[string]ssh.ChannelHandler{
			"direct-tcpip": ssh.DirectTCPIPHandler,
			"session":      ssh.DefaultSessionHandler,
		},
	}
	server.SetOption(publicKeyOption)
	//server.SetOption(ssh.HostKeyFile("/data/data/com.termux/files/usr/etc/ssh/ssh_host_dsa_key"))//not supported by x/crypto/ssh
	server.SetOption(ssh.HostKeyFile("/data/data/com.termux/files/usr/etc/ssh/ssh_host_rsa_key"))
	server.SetOption(ssh.HostKeyFile("/data/data/com.termux/files/usr/etc/ssh/ssh_host_ecdsa_key"))
	server.SetOption(ssh.HostKeyFile("/data/data/com.termux/files/usr/etc/ssh/ssh_host_ed25519_key"))
	log.Println("starting ssh server...")
	log.Fatal(server.ListenAndServe())
}

自分は他のものと一緒に動かしているので、該当の関数だけ書きました。importなどについてはGitHubのサンプルを参照してください。

phantom process killing対策 ②sleep 180を組み込みコマンドに

起動中、ps auxしてみると、sleep 180というプロセスがあることがわかります。これはクライアント側の~/.vscode/extensions/ms-vscode-remote.remote-ssh-0.xxx.x\out\install-script\scripts\linux-exec-server-installer.shの最後の

while true; do sleep 180; printf ' '; done

に由来するもので、VS Codeのウインドウ1つごとに新規に立ち上げられ、アイドル状態での待機のようなことを行っているようです。ウインドウを閉じてしばらくすると親プロセスであるシェルごと消えます。

sleepと同じことを組み込みコマンドでやるにはBash/Zsh + POSIX で sleep 0.01 する方法 #ShellScript - Qiitaのようにreadを使う方法があり、これを使えばsleepのプロセス数を削れます。ついでに秒数も短くしておくと早めに消えてくれます(詳細な仕様は不明)。ただしこのreadの-tオプションはbashなど一部のシェルでしかサポートされていないことには注意が必要です。

実際にreadに置き換えるにあたっては、まず以下のような内容で、$HOME/.vscode-server/vscode-sleep (場所はどこでもOK)を作成しておきます。

unset BASH_ENV
function sleep {
    if [ "$1" = "180" ]; then
        mkfifo $PREFIX/tmp/sleep.tmp
        exec 9<> $PREFIX/tmp/sleep.tmp
        rm -f $PREFIX/tmp/sleep.tmp
        read -t 10 <&9
    else
    /bin/sleep $1
    fi
}

秒数で分岐させているのは、linux-exec-server-installer.shにはsleep 1やsleep 3なども含まれているからです。

そして、既に載せた通り、ssh-login.shでこのファイルパスをBASH_ENVに設定した上で、exec bashとしています。非インタラクティブシェルなので--rcfileだと効果が出ないようです。先頭でBASH_ENVをunsetしているので、統合ターミナルなどに影響することはありません。

ちなみにsleepやreadではなくwaitなどを使うと、ウインドウを閉じてもいつまで経っても親プロセスであるシェルが生き残ってしまうのでダメです。

その他エラー

これも【Android】非rootなTermuxでTailscaleを動かす - turgenev’s blogの通り、最近のAndroidではネットワークインターフェース取得のAPIが変わったせいで、VS Codeでも

A system error occurred: uv_interface_addresses returned Unknown system error 13 (Unknown system error 13)

というエラーが出ていますが、今のところ具体的な悪影響はありません。

(追記)ssh-agentおよび後日の記事の内容に対応

ssh-agent、およびこれより後に公開した普通のSSHから"code"コマンドでVS Code Remote SSHを起動する方法 - turgenev’s blogの記事の内容への対応に関して若干補足します。

まず後者の記事に関してですが、基本的には環境変数や自動実行のたぐいはssh-login.shでなんでも設定できるのでそんなに苦労することはないです。ただし、gliderlab/sshを使う場合、Unixドメインソケット転送のプルリクがマージされていないので自分で取り込む必要があるのと、記事のように同一パスにUnix socketを複数回転送しようとすると拒否される仕様になっている(OpenSSHはそうではない)ので修正します。

また、ssh-agentに関してですが、上の記事で書いた通り、VS Codeの統合ターミナルに設定されるSSH_AUTH_SOCKは、VS Codeから見たときの元のSSH_AUTH_SOCKへのシンボリックリンクのパスになっています。このシンボリックリンクがXDG_RUNTIME_DIRに置かれるのでssh-login.shでの設定が必要だったわけです。

ただ、ここで1つバグがあり、元のSSH_AUTH_SOCKの長さが64文字を超えるときに、65文字目が2つ並んだ誤ったパス(存在しないパス)がシンボリックリンクのリンク先に設定されてしまいます。Termuxのパスは基本的に/data/data/com.termux/files/...みたいなのが付いていて長くなりがちで、普通のsshdでもgliderlabs/sshでも64文字を若干超えてしまいます。

しかし、ちょうど上記の自分の記事のように(SSH_CONNECTIONを得る目的で)SSH_AUTH_SOCKへのシンボリックリンクをもっと短いパスにして自前で設定しておけば、この問題もついでに解決します。

(追記)F-05Jでの動作確認

かなり古いAndroid 8(カーネルは3.18.71)の富士通F-05Jも所有しているので試してみました。
今までの方法でいくと、ダウンロードまではいけますが、code-serverを起動した直後に~/.vscode-server/code-xxxxxxが死んでしまうらしく(Exec server for ssh-remote+myserver closed (gracefully) )、うまくいきません。straceも権限がなくて使えないようなので詳しい原因究明も困難です。
ただし、このcode-xxxxxxを使うのは新しい方式で、remote.SSH.useExecServerを無効にすれば使用されなくなります。これは全リモート・プロファイル共通の設定ですが、user-data-dir=を指定すれば同一ユーザー内で別の設定を維持できます。
あとは、VS Codeでの接続時のsshのコマンドラインがssh -T -D 54321 example.com bashなどと最後がbashに変わるので、ssh-login.shでそれに対応する必要があるのと、フォルダ構造も大きく変わるのでshebangを直すスクリプトも変更が必要になります。ただしこちらの方式の場合は起動に失敗してもクリーンアップされないようなので、失敗してから手動で修正して改めて接続し直すのでも問題ありません。
また、いつから変わっているのかわかりませんが、/bin/shが存在しないようなのでそこも変えます。
Android 8なのでphantom process killingはありません。
変更後のshebang修正スクリプトはこんな感じになりそうです。xxxxxx.stagingとかも作られないのでwgetのコマンドラインからハッシュを取っています。
#!/data/data/com.termux/files/usr/bin/sh
while true; do
    HASH=$(ps -ef | grep "[w]get" | grep "vscode-server.tar.gz" | grep "commit" | sed -n 's/.*commit:\([a-f0-9]\{40\}\).*/\1/p')
    if [ -n "$HASH" ]; then
        echo hash:$HASH
        cd ~/.vscode-server/bin/$HASH
        while true; do
        if [ -f "bin/code-server" ]; then
            sleep 0.001
            termux-fix-shebang "bin/code-server"
            exit
        fi
        sleep 0.001
        done
    fi
    sleep 1
done
あるいはさきほどの追記内容のように自滅型のスクリプトを置くこともできます。しかし、さっきと違ってリネーム元のフォルダがすぐに削除されてしまうようです。そこで、wgetの段階でアーカイブファイルを移動して退避させ、シンボリックリンクを貼ります(wgetはすでにファイルディスクリプタを持っているので正常に続行される)。また、code-serverより先にbin/helpers/check-requirements.shというのが実行されるのでそちら側に自滅スクリプトを配置します。
まず以下のファイルがvs-check-requirements.sh-initです。
!/data/data/com.termux/files/usr/bin/sh
#remove myself and run the real code-server after fix-shebang
cd $(dirname $0)/../..
tar -xf bak.tar.gz \
    --strip-components=1 \
    -C . \
    vscode-server-linux-arm64/bin
termux-fix-shebang bin/code-server
termux-fix-shebang bin/remote-cli/code
sed -i '1a unset LD_PRELOAD' bin/remote-cli/code rm bak.tar.gz exec bin/helpers/check-requirements.sh "$@"
次に、それと同じディレクトリに以下のスクリプトを配置します。
#!/data/data/com.termux/files/usr/bin/sh
# This version is more stable because there are a lot of time during wget download.
# However, uses more CPU and disk IO for extracting tar again.
cd $(dirname $0)
INIT_SCRIPT=$(pwd)/vs-check-requirements.sh-init
while true; do
    HASH=$(ps -ef | grep "[w]get" | grep "vscode-server.tar.gz" | grep "commit" | sed -n 's/.*commit:\([a-f0-9]\{40\}\).*/\1/p')
    if [ -n "$HASH" ]; then
        echo hash:$HASH
        cd ~/.vscode-server/bin/$HASH
        while true; do
        if [ -f "vscode-server.tar.gz" ]; then
            mv vscode-server.tar.gz bak.tar.gz
            ln -s bak.tar.gz vscode-server.tar.gz
            mkdir -p bin/helpers
            cp $INIT_SCRIPT bin/helpers/check-requirements.sh
            touch bin/code-server
            exit
        fi
        sleep 0.1
        done
    fi
    sleep 0.1
done
sleepの時間に余裕があるかわりにtarの展開をもう一度やらなければいけない(tar.gzは構造上、一部ファイルの取り出しでも全体を解凍する必要がある)のはあまり嬉しくはないです。



以上の内容はhttps://turgenev.hatenablog.com/entry/2026/02/11/121330より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14