たるにっき

Technology Acquisition Log 日記.

WSL の /mnt 以下で chmod が効かず ssh-keygen に失敗する問題

WSL の /mnt 以下で chmod が効かず ssh-keygen に失敗する問題

目次


背景

秘密鍵を PKCS8 形式に変換する必要に迫られる場面があり、WSL 上で作業しようとしたところ、/mnt/c/Users/.../.ssh/ 内のファイルに対して ssh-keygen が失敗。 調べると chmod も効いておらず、昔から存在する WSL の既知の問題だった。 どうせまた引っかかる罠なので記録を残しておく。


原因

WSL の /mnt/ 以下は DrvFs というドライバーで Windows のファイルシステムをマウントしている。 デフォルトでは「Windows のファイルに Linux のメタデータは不要」という前提でマウントされており、Linux のパーミッション情報(メタデータ)を保持しないため、chmod を実行しても変化しない。 結果、すべてのファイルが 0777 に見え、ssh-keygen のような「パーミッションが厳密でないと動かないコマンド」が軒並み失敗する。


対処法

/etc/wsl.conf を編集して WSL 側の設定を明示的に変更し、Windows ファイルシステム上でも Linux のパーミッション情報を扱えるようにする。 これにより DrvFs マウント時に Linux のパーミッション情報が保持されるようになり、chmod が有効になる。 あくまで WSL の動作設定であり、Windows 側のファイルや権限設定には影響はない。


問題の確認

1. ssh-keygen を試みるも失敗

$ cp id_rsa id_rsa_pkcs8.pem
$ ssh-keygen -p -m PKCS8 -f id_rsa_pkcs8.pem
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0777 for 'id_rsa_pkcs8.pem' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Failed to load key id_rsa_pkcs8.pem: bad permissions

2. chmod を試みるも反映されない

$ chmod 711 id_rsa_pkcs8.pem
$ ls -l
-rwxrwxrwx 1 budoucha budoucha 1642 Nov 25 14:29 config
-rwxrwxrwx 1 budoucha budoucha 3389 Nov 11 11:01 id_rsa
-rwxrwxrwx 1 budoucha budoucha  751 Nov 11 11:01 id_rsa.pub
-rwxrwxrwx 1 budoucha budoucha  800 Mar 18 11:31 id_rsa_pkcs8.pem

chmod 後も全ファイルが rwxrwxrwx のまま。


解決手順

3. /etc/wsl.conf を編集

$ sudo nano /etc/wsl.conf
[boot]
systemd=true

[automount]
enabled = true
options = "metadata,umask=22,fmask=11"

各オプションの意味:

オプション 意味
metadata Linux のパーミッション情報を Windows ファイルシステム上に保持できるようにする。これを追加することで chmod が有効になる
umask=22 ファイルシステムのデフォルトパーミッションを初期値-22にする(777 - 022 = 755)
fmask=11 ファイルのデフォルトパーミッションを初期値-11にする(666 - 011 = 644)

4. WSL を再起動して設定を反映

PowerShell 側で実行:

> wsl --shutdown
> wsl

再起動後、改めて chmod を実行すれば正しく反映される。


備考

  • この問題は WSL2 が登場した頃からある既知の挙動で、2026年現在もデフォルト設定は変わっていない
  • /mnt/ 以下ではなく WSL のネイティブホーム(~/)に鍵をコピーして作業する、という回避策もある(以前はそれでなんとかした事を今思い出した)
  • Windows 側の OpenSSH Agent と共有する構成にする方法もあるが、そちらは別途
  • 本記事はチャットでの相談と作業ログを元に Claude が起稿したものを修正したものです。

Windows Terminalの不透明度はJSONファイルで設定する、わけではなかった

7/9 追記: 透明度の設定、ありました。「外観」ではなく、「既定値>外観」にありました。紛らわしいことするんじゃねえよ

 

ペイン分割に惹かれてメイン環境でWindows Terminalに移行したものの、半透明化する方法が分からず困っていた。

設定画面には不透明度の項目が見当たらず、以前と同じ方法での設定はできなかった。

→設定画面ではなくJSONファイルの直接編集で不透明度を変更できる。


問題点

  • Windows Terminalの設定UIに不透明度(透過率)の項目が存在しない

解決方法

  1. 設定タブを開く(Ctrl+,)
  2. 追記:ここで設定できます

    Windows terminalの透明度の設定方法

     

    という事が後から判明しました。
    なので以下はもう読まなくてよくなりました。

  3. 設定画面の左下にある「JSONファイルを開く」から、設定ファイル(settings.json)を開く

  4. profiles.defaultsを編集して保存

元の状態

 "profiles":
    {
        "defaults":
        {
            "colorScheme": "Campbell",
      },

半透明化(従来風)

 "profiles":
    {
        "defaults":
        {
            "colorScheme": "Campbell",
            "opacity": 72,
        },

 

半透明化(すりガラス風)

 "profiles":
    {
        "defaults":
        {
            "colorScheme": "Campbell",
            "useAcrylic": true,
            "acrylicOpacity": 0.5
      },

(検索するとこちらの方がヒットしやすいが、あまり好みではない)

 

保存した瞬間に反映される(再起動不要なのは地味に嬉しい)

 

補足

defaultsの構造からも分かる通り、不透明度の設定は配色(colorScheme)とは独立している。使う配色を変更する際に改めて設定を変える必要はない(配色ごとに最適な透過率を設定したい場合は不便かもしれない)。

shとbashの話、./とシバンの話、sourceの話

コマンドライン上でシェルスクリプトを起動する方法にはshbash./などがある。

自分の場合、これまで特に何も考えず手癖でsh hoge.shで起動することが多かった。

理由は./と比べると作ったファイルが実行可能形式になっているかどうかを気にせず有無を言わさず実行でき、bashよりは打鍵数が少ないためで、これまでそれで特に困ることはなかったので、各方法の違いを意識する機会もなかった。 それほど頻繁にシェルスクリプトを書くこともなかったし。

今回久々にシェルスクリプトChatGPTの出力を何も考えずに貼り付けたら書いたらこの違いで困ることになったので、知る機会になったので備忘録として残す。

dashについて。

Ubuntushコマンド、つまり/usr/bin/shbashではなくdashへのシンボリックリンクになっている(/bin/sh も同様)。

$  which sh
/usr/bin/sh
$ ls -l /usr/bin/sh
lrwxrwxrwx 1 root root /usr/bin/sh -> dash

CentOS系ではshとはbashである事を当然のこととして認識し、Ubuntuもユーザーシェルがbashであると知っていたため自分はUbuntuのshコマンドもbashを呼び出していると勘違いしていた。

そもそもdashを知らなかったのだが、bashより軽量で、その代わりbashで使える拡張された書き方が使えないらしい。
シェルスクリプト内でそのようなbash特有の文法を使っているせいで、スクリプトをshで実行したときに想定通りに動作しない事がある。
そのような場合はシバンやコマンドを変え、shではなくbashを指定すると動くようになる。

今回、いくつかそういう箇所があったが、その内の一つが、sourceコマンドだった。 sourceコマンドは、bashでは有効なコマンド扱いだが、dashで使おうとするとcommand not foundとなる。 なのでシバンを書き換えたのだが、ここでまた理解不足により混乱することになった。

shebangについて

シェルスクリプトの冒頭に書くシバン(#!/bin/bash)は使用するシェルを明示するためのものだが、これは 実行可能形式のファイルを./foo.sh などのように直接実行する場合のみ効果がある。

$sh foo.sh のようにコマンドでスクリプトを実行する場合、書かれたシバンに関係なく、コマンドのに該当するシェルが選択されるため、シバンは意味をなさない。 これまでただ漠然と「起動するシェルはシバンで選べるらしい(シェルの違いは分からんけど)」くらいにしか認識しておらず正しく理解していなかったので、 今回、dashではなくbashを必要とする事が判明してとりあえずシバンを修正してみたものの、./hoge.shではなくsh hoge.sh`で起動してしまっていたので効果がなく、混乱した。

sourceについて。

冒頭でシェルスクリプトの起動方法について、sh./で起動できることは述べたが、実はsourceでも実行できる。

sourceというと「~/.bashrc~/.bash_profileを書き換えた後に読み込むときのコマンド」のイメージだったが、それらのファイルもコマンドの集合であり、要は対象のファイル内のコマンドを実行しているのがsourceである。 今回、ChatGPTが出力してきたコード内で、別のシェルスクリプトsourceで呼び出しているのを見たおかげで忘れかけていた事を思い出すことができたが、そもそもなぜsourceを使ったのだろうか気になった。そのせいでdashとbashの違いやシバンで苦しむことになった。

ググった。

shとsourceの違い

まとめると、

./sh は新たな子プロセスを生成して実行するが、sourceは現在のシェル中で実行される。

これだけだと実用上の違いが分かりにくいが、スクリプト内で変化した変数が、 スクリプトの実行終了時に用済みとして破棄されず、残り続けるのがsourceである。

こういうことらしい。

$ cat test.sh
X=123
$ bash ./test.sh
$ echo $X
← 何も定義されていない
$ source ./test.sh
$ echo $X
123 ← スクリプト内で定義したとおり
(上記記事から引用)

まとめ

ふわっとした知識は固めると良い

Docker公式のgetting startedが動かなくても慌てなくていい

最後にDocker触ってから大分経ってて色々忘れてたので公式のgetting startedをちょろっとつまみ食いしておこうとして危うく沼にはまりかけた。

Containerize an application | Docker Documentation

Dockerfileをコピペしてimageをビルドしてcontainerを起動、それだけの話なので特に何事もなく済んで…ほしかったけど起動したコンテナがすぐ落ちる(ああこれ懐かしい)

まああまりメンテできてないんだろうな、と色々弄ってビルドと起動を繰り返したりをしたもののdocker container prune -fを何回も打ちこみながらこれこれ懐かしー!ってなっただけで解決せず。

結局 docker run -d -p 80:80 docker/getting-started で直接イメージ落としてきたら動いたので何が良くなかったんだろうと GithubのDockerfileを覗いてみたらAlpineベースとかyarn使ってるのとかは同じだったけど行数が結構増えていた。 それだけ。

JSのセミコロン省略について

最近身近で「JSのセミコロンって要るの?意味あるの?」という質問があり、 漠然と「無くてもいいっぽいよ」という認識しかなかったため調べてみました。

ちなみに自分は最近は書かないことが多いです。

2015年なので少し古いですが、この記事が良かったです。
JavaScriptの行末セミコロンは省略すべきか | blog.tai2.net

大体以下のような内容でした。

  • 本来は文の終わりを示すのにセミコロンは必要
  • JSにはAuto Semicolon Insertionという実行時に必要であればセミコロンが適宜自動で挿入される機能がある
  • このため基本的に文の終わりが行末と一致する場合は省略可能
  • ただしASIが意図せぬ挙動の原因となる事がある
    • return{ の間に改行がある場合、return直後にセミコロンが挿入されundefinedが返る
    • (function(){...})() のような記述は、その前の行の行末が変数の場合(a = b + cなど)セミコロンが挿入されず行末の変数を関数として呼び出す(c(...))と解釈される
  • このような罠のためASIを動作させるような書き方(セミコロン省略)は良くないとする派閥がある
    • ASIの作者自身、積極的にセミコロンを省略するのは安全ではないと言っている
  • 一方でnpmやbootstrapの開発者などセミコロン省略派も少なくない
  • ASIが悪さをするような改行の仕方はそもそも良くない書き方なので避ければいい
    • return後改行の問題はセミコロンを付けても回避不可能なのでしてはいけない
    • 関数呼び出しにされるのは!(function(){...})と書けば回避可能
      • これについては批判もある
  • Javascriptは多様な書き方を許容する言語であり、どちらの流派も一理ある

セミコロン省略したいな派としては、ASIについて知らなかったのは不味かったなと思いました。
以下は感想です。

  • 初学者にとっては重箱の隅なので取り敢えずセミコロンは付けさせて慣れてきたら自己責任がいいのかな
  • return 直後に改行するな
    • そもそも返り値はreturnより上で定義した方が綺麗じゃないですか?
  • 即時関数の解釈違いを!で回避する方法については正直ちょっと苦しいと思った
  • 綺麗な書き方をすれば綺麗に動くんですよしらんけど
  • ESLINT使っとけばまず問題起きないでしょしらんけど

C/C++の宣言時constと型はどっちを右に書くか

興味深い投稿を見ました。

'const int' vs. 'int const' as function parameters in C++ and C

C/C++では定数宣言時の記述って const intint const のどっちが正しかったっけと思ったら、どちらも同じように機能するそうです。

個人的には前者の方がこれはイミュータブルな値だよって明示してる感があるので好みですね。

ところでポインタが絡んでくると微妙に事情が変わってくるみたいです。

const char* is a pointer to a constant char

char const* is a pointer to a constant char

char* const is a constant pointer to a (mutable) char

基本的にはconstなcharへのポインタになりますが、char* constと書いた場合のみ(ミュータブルな)charへのconstなポインタになるということみたいです(機械翻訳みたいになってしまった)

要はポインターがconst、つまり後から変えることが出来ないアドレスを宣言するときは3番目を使うようです。

回答者によれば多くの人はこの理由でconstを右側に書く記法を好むらしいです。

イミュータブルなchar型の値でだよ、ではなくchar型のイミュータブルな値だよ、の方が一般的ということですね。

頭の片隅に残しておこうと思…っても多分残らないと思うのでここに残しておきます。

Visual Studio 2019を入れたりC++触ったり

特に躓くこともなかったので単なる記録。

 

数年ぶりにC/C++また触ってみようと思い立つ(またといいつつC++は殆ど触ったこと無いけど)。

別にgccCLIでも良かったけどこの辺でIDEちゃんと使えるようになっときたいのでVisual Studio 2019をインストール。

 

CodeじゃないVisual Studioを入れるのは数年ぶり。

過去のPC何台かにインストールしたときは使い方よく分かんないくせにクッソ重いソフトみたいな印象しかなかったが、いま入れてみるとVM動かしてる横で起動してもそこまで時間かけずに起動するし(流石にVS Code並とは行かないけど)、動作も特に重いとかもなく普通に使えて少し拍子抜けした。

あとプログラムの実行の仕方が最初からコメントに書いてあって親切だなと思った

f:id:budoucha:20210707063203p:plain

初回立ち上げからソリューションがどうこうあたりまでの操作は以下のサイトを参考にした。

非情報系学生のための C/C++ 入門

 

ついでに流し読み。

  • <<や>>のストリーム演算子は複数連結できる。
  • 名前空間はusing namespaceで明示できる。
  • ポインタの*は int *p とも int*p とも int * pとも書ける。
  • 宣言時の*pと後で使うときの*pは同じ表記だが意味が違う。
  • int *pは*pという変数を宣言しているわけではなく、この「*」は「pがポインタ型変数であることを示す」符号。
  • *pではなくp自体がポインタ型変数なので、アドレスの代入はp=&xのように「*」抜きで表記。
  • *p=5 や cout << *p などと書くときのの「*」は「pのアドレスの場所の中身を操作しますよ」記号。宣言時の「*」とはポインタ関連の操作ですよくらいしか合ってない。
  • 配列の宣言時に長さを変数では指定できない。Array[n]みたいなのを作るならnを定数にするか、int* a = new int[n]などのようにnewを使う。