Windows Terminal起動時にIMEがオンになってしまう問題

Windows Terminal 起動時に自動でIMEがオンになってしまう問題に遭遇したので、対処方法を残す。

IMEがオンになっている図

上記画像のように、起動時に「半角英数モード」であるもののIMEがオンになってしまった。 先人の情報を検索するに、Google日本語入力を利用している環境で発生する問題である雰囲気がある。

zenn.dev

GithubのIssueも存在している。

github.com

原因を少し掘り下げ

IME周りの操作についての知識がないため、完全に予想でしかないけれども、v1.14.143で入った下記の修正の影響かもしれない。IMEの状態を AlphanumericHalfWidth にする変更であるが、Google日本語入力においては 半角英数直接入力 が別の状態で存在しており、半角英数を指定されることでIMEがオンになってしまうと思われる。 (参考: Gboardヘルプ: Windows Terminal 起動時の 入力方法 に関する不具合

github.com

暫定対処

v1.13.11431.0 を利用する。

ただし、Microsoft Storeで自動更新される雰囲気があるので、自動更新を無効にしておかなければ、また同様の問題が発生するようになる。Microsoft Storeでは個別のアプリごとに自動更新を停止することはできない(参考リンク)ようなので、設定から全ての更新を無効化する必要がある。

Flutterを使ったファミコンエミュレータの開発 (HelloWorldまで)

概要

Flutter(Dart)の勉強をしようと考え、どうせなら以前から作りたいと思っていたファミコンエミュレータを書くことにした。 まだROMとしてはHello Worldを表示するテストROMしか動かせてはいないが、60FPSで描画出来るようになったので記事を書いた。

github.com

Hello World ROMが動いている様子

ファミコンエミュレータについて

先人の皆様がファミコンエミュレータを様々な言語や環境に移植しつつ、それに伴う知見をブログなどに残してくださっているので、それらの情報に従ったら基本的に作成できる。私の場合は、下記の記事を一番参考にさせてもらった。

qiita.com

CPU/PPUのそれぞれの仕組みから、HelloWorldを表示するまで必要な内容をわかりやすく説明してくださっている。 まずは記事に従ってROMに含まれるスプライトを表示してみるのが良い。実際に目に見えるものが出てくるとテンションが上がるのもある。

記事の最後にサンプルROMのアセンブラが乗っているので、自分のCPUが吐き出したオペコードと比較して何が間違っているかを確認してすすめることができた。

詳細なCPUの命令ごとの動作については下記を参考にさせてもらった。

NES on FPGA CPU

レジスタのステータスフラグの意味や、各命令でしなければならないこと、アドレッシングモードの種類やサイクル数など、ここを見れば基本的に問題ないはず。 私の場合はオペコード毎の情報を格納したMAPを作るにあたって、このサイトに加えて、下記の2サイトも参考にした。

6502 Opcodes - NES Hacker Wiki

6502 instructions - Nesdev wiki

ただ、Hello Worldを動かすにあたっては全ての命令とアドレッシングモードを実装する必要はまったくなかったので、下記の命令とアドレッシングモードを実装するところまでで止めている。アドレッシングモード毎にFetchしなければならない数が決まっているのでそこと、Reset割り込みにおけるPCの初期値セットだけを実装し、頭からFetchしていくことで、必要な命令とアドレッシングモードを抜き出すことができた。(最終的にはJMPで無限ループになるので、そこまで抜き出す。)

# 命令
BNE
DEY
INX
JMP
LDA
LDX
LDY
SEI
STA
TXS
# アドレッシングモード
Implied
Accumulator
Immediate
Relative
Absolute
AbsolutePageX

Flutterにおけるエミュレータ

エミュレータ実行スレッドを分ける

エミュレータ自体の実行はそれなりに処理負荷がかかるので別のIsolateで実行するようにした。 ゲーム実行中はずっと動かし続けるので compute 関数を使うのではなく、Isolate.spawn によってIsolateを立ち上げる。

emulatorReceivePort = ReceivePort();
Isolate.spawn(emulatorIsolateMain, emulatorReceivePort.sendPort);

emulatorIsolateMain 側でも ReceivePort を生成し、それをParent側に送信することで双方向の通信が出来るようにしている。 エミュレータ実行Isolate側では下記のコードのように常にParentからのイベントを待機しながら、エミュレータの1フレームごとの処理を行っている。

Timer.periodic(Duration(milliseconds: 16), _executeFrame);

await for (var message in childReceivePort) {
  // メッセージが来たら処理を行う
}

1フレーム分の処理が完了したら、Isolate作成時に渡された sendPort を使って、メインIsolateに向けてフレームのピクセル情報を送信する。

画面への描画

エミュレータ側で RGBA 形式の Uint8List を作成し、それを ui.decodeImageFromPixels によって ui.Image 形式に変換させる。 (256x240はファミコンの画面サイズ)

Future<ui.Image> _convertFrameToImage(Uint8List pixels) {
  final c = Completer<ui.Image>();
  ui.decodeImageFromPixels(
    pixels,
    256,
    240,
    ui.PixelFormat.rgba8888,
    c.complete,
  );
  return c.future;
}

その後 ui.ImageCustomPaint におけるCanvasに対して canvas.drawImage している。 当初は RawImage に直接 ui.Image をつっこんで、毎回 setState() {}により描画処理を発火させていたが、おそらく CustomPaint を使いrepaint させるほうがインスタンス化のコストが減り、低負荷なのではないかと考えてこのような形にした。(未検証なので実は RawImage でも軽かったりするかもしれない。)

class _EmulatorPageState extends State<EmulatorPageWidget> {
  _EmulatorController controller;

  @override
  void initState() {
    super.initState();
    controller = _EmulatorController();
  }

  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: <Widget>[
          Expanded(
            child: Container(
              width: double.infinity,
              height: double.infinity,
              color: Colors.black38,
              child: CustomPaint(
                painter: _EmulatorDrawPainter(controller),
              )
            )
          ),
        ]
      ),
    );
  }
}

class _EmulatorController extends ChangeNotifier {
  ui.Image currentFrame;

  _EmulatorController();

  // Isolate周りの処理は省略

  /** 
   * Isolateからフレーム更新のメッセージが届いた場合に発火する
   */
  void _onRecvUpdateFrame(Uint8List framePixels) {
    _convertFrameToImage(framePixels).then((ui.Image image) {
      currentFrame = image;
      notifyListeners();
    });
  }
}

class _EmulatorDrawPainter extends CustomPainter {
  _EmulatorController controller;
  Paint paintObject;

  _EmulatorDrawPainter(this.controller): super(repaint: controller) {
    paintObject = Paint();
  }

  @override
  void paint(Canvas canvas, Size size) {
    canvas.save();
    if (controller.currentFrame != null) {
      canvas.drawImage(controller.currentFrame, Offset.zero, paintObject);
    }
    canvas.restore();
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

CustomPaint において描画をし直す = paint を再度呼び出すためには repaint オプションに ChangeNotifierValueNotifier を渡す必要がある。 これらのNotifierを使ってデータの更新があることを CustomPaint に通知すると、paint が再実行される。(ValueNotifier の場合はそのままデータを設定すると setter の働きによりそのまま通知が行われる。 ChangeNotifier の場合は notifyListeners() を実行すれば良い。

手を抜いた点と、今後の予定

PPUについてかなり手を抜いた。 パレット情報を使っていないし、1フレーム分のサイクルが走ったら、そのタイミングで全部描画するという実装にしている。 今後、実際のゲームROMを実行する段になって問題が出ると思うので、そのタイミングで実装する。

最終的にはMapper4のROMが動作するところまで実装したい。 その後、GameBoyColorのエミュレータ作成にもチャレンジしてみたい気持ちである。 (とりあえず吸い出し機とゲームは購入した)

PrometheusとGrafanaで複数台のサーバを監視する

f:id:riosu:20200423184008p:plain

趣味のサービスを運用しているが、何らかの監視をしたい。 しかし、DataDogやNewRelic、Mackrelのように有償のサービスを使うほどではない。 けれども、Zabbixはなんか嫌だったので、Prometheus + Grafanaで監視の仕組みを構築した。

構成

  • 監視用サーバ Ubuntu16
    • nginx
    • docker
      • Prometheus
      • Grafana
    • node_exporter
  • 監視対象サーバ1 Ubuntu16
    • node_exporter
  • 監視対象サーバ2 Ubuntu16
    • node_exporter

監視用サーバの設定

docker-compose.yml

PrometheusとGrafanaをDockerで構築する。

両方ともデータを永続化するために volumes を作成して、データが保存されるフォルダにマッピングさせている。

今回Grafanaはサブディレクトリ運用をするため、 GF_SERVER_ROOT_URL をこのように設定した。

version: '3'
services:
  prometheus:
    image: prom/prometheus:v2.17.2
    ports:
      - 9090:9090
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
  grafana:
    image: grafana/grafana
    ports:
      - 3000:3000
    environment:
      GF_SERVER_DOMAIN: "manage.example.com"
      GF_SERVER_ROOT_URL: "https://manage.example.com/grafana"
    volumes:
      - grafana_data:/var/lib/grafana
volumes:
  prometheus_data:
  grafana_data:

/etc/nginx/sites-enabled/default

nginxでリバースプロキシする。今回はHTTPSでかつBasic認証を設定した。(これらの設定はこの記事では対象としない)

ただしGrafanaをBasic認証を経由して使う場合は proxy_set_headerAuthorization を空文字にしておく必要がある。 Authorization ヘッダーがGrafanaまで届いてしまうと Invalid username or password というエラーが出るためである。

{
    listen 443 default_server ssl http2;
    server_name manage.example.com;

    ssl_certificate /etc/letsencrypt/live/manage.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/manage.example.com/privkey.pem;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;
    ssl_ciphers AESGCM:HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers  on;
    ssl_verify_client off;

    auth_basic "Secret";
    auth_basic_user_file /etc/nginx/global_htpasswd;

    charset utf-8;
    access_log      /var/log/nginx/manage.access.log;
    error_log       /var/log/nginx/manage.error.log;

    location /grafana/ {
        proxy_set_header Authorization "";
        proxy_pass http://localhost:3000/;
    }
}

prometheus.yml

ターゲットとなるIPアドレスは各自の環境に合わせて設定すること。

global:
  scrape_interval: 15s

  external_labels:
    monitor: 'codelab-monitor'

scrape_configs:
  - job_name: 'prometheus'
    scrape_interval: 5s
    static_configs:
      - targets: 
        - prometheus:9090
  - job_name: 'node'
    scrape_interval: 5s
    static_configs:
      - targets: 
        - 192.168.0.2:9100
        - 192.168.0.3:9100
        - 192.168.0.4:9100

参考サイト

github.com

監視対象サーバの設定

NodeExporterはDockerではなくUbuntu上でそのまま動かす。 下記のサイトのやり方をそのまま踏襲した。

www.tmp1024.com

wget https://github.com/prometheus/node_exporter/releases/download/v0.18.1/node_exporter-0.18.1.linux-amd64.tar.gz
tar -zxvf node_exporter-0.18.1.linux-amd64.tar.gz
sudo mv node_exporter-0.18.1.linux-amd64/node_exporter /usr/local/bin/
sudo vim /etc/systemd/system/node_exporter.service
sudo systemctl daemon-reload
sudo systemctl start node_exporter.service
sudo systemctl enable node_exporter.service

/etc/systemd/system/node_exporter.service

[Unit]
Description=Node Exporter

[Service]
Type=simple
ExecStart=/usr/local/bin/node_exporter
PrivateTmp=false

[Install]
WantedBy=multi-user.target

Grafanaでのデータソース追加

基本的なGrafanaの使い方はインターネット上の別の記事に任せるが、一点だけ注意する点として、URLを http://prometheus:9090 にする必要がある。Docker上のGrafanaがPrometheusにアクセスする場合、 localhost では駄目である。

f:id:riosu:20200423183509p:plain
Grafana Datasource

CakePHP3をDocker上で簡単に動くようにする

概要

CakePHPを使って何かしらのウェブサービスを作ろうかと思った時、最初の環境構築が面倒。 ローカル環境でMySQLを建てるのも面倒くさいし、環境が汚れるのも嫌だし、最近だとやっぱりDockerを使って開発したい。 この記事では、CakePHP3のプロジェクトを作成してからDocker上でアプリを表示できるまでの手順を記す。

CakePHPプロジェクトの作成

まずはCakePHPプロジェクトの作成を行う。Dockerコンテナの中にcomposerを入れてそこでプロジェクトを作成する方法もあるが、Macにおいてコンテナ内とのファイルの共有処理はとても遅いため、今回はMacにcomposerを入れて、それを使ってプロジェクトを作成することとした。

composer create-project --prefer-dist cakephp/app app

Docker環境の準備

これからの作業は全てプロジェクトのディレクトリで行う。

mkdir app

まず、Dockerfileを作成する

cd app
vim Dockerfile

中身はこんな感じ。

FROM php:7.3.11-fpm

# Install some libraries and extensions
RUN apt-get update && apt-get install -y \
    libicu-dev \
    unzip \
    git \
    curl \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-install intl pdo pdo_mysql

# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

RUN mkdir -p /app
WORKDIR /app

ベースとしているのは php:7.3.11-fpm イメージ。別のバージョンが使いたければ変えれば良し。 その後CakePHPを動かすために必要なPHPの拡張をインストールしている。 このイメージでは docker-php-ext-install を使うことで簡単に導入可能である。 その前段でインストールに必要なパッケージを apt で入れているが、同じ RUN 内で apt-get cleanrm -rf /var/lib/apt/lists/* することでDockerイメージを小さくしている。

続いて docker-compose.yml を準備する

vim docker-compose.yml

内容は以下の通り

version: "3"
services:
  app:
    build: .
    depends_on:
      - mysql
    volumes:
      - ./:/app
    environment:
      DEBUG: "true"
      APP_DEFAULT_LOCALE: ja_JP
      APP_DEFAULT_TIMEZONE: Asia/Tokyo
      DATABASE_URL: mysql://root:root@mysql/database?encoding=utf8
  web:
    image: nginx
    depends_on:
      - app
    ports:
      - 8001:80
    volumes:
      - ./:/app
      - ./.docker/nginx.conf:/etc/nginx/nginx.conf
  mysql:
    image: mysql:8.0.18
    volumes:
      - ./.docker/mysqld_add.cnf:/etc/mysql/conf.d/mysqld_add.cnf
    ports:
      - 3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: database

CakePHP環境変数を渡したい場合は appenvironment に追記すれば良し。 今回はローカル開発ということで、DBパスワードも適当にしているが、公開する場合等は注意すること。 nginxの待ち受けるポートは 8001 にしているが、ここは自由に変えてまったく問題がない

続いて nginxmysql 用のコンフィグファイルを配置する。 Dockerで使うファイルを置くディレクトリとして .docker フォルダを作成し、その中に置くこととする。

mkdir .docker
vim .docker/mysqld_add.cnf
vim .docker/nginx.conf

.docker/myqsld_add.cnf には下記のように書いておく。 MySQL8からログイン方法のデフォルト値が変わった関係で、この設定を入れておかないとPHP側から接続することができないようだ。これを mysqld コンテナの /etc/mysql/conf.d/ 以下にマウントすることで反映させている。

[mysqld]
default_authentication_plugin=mysql_native_password

続いて .docker/nginx.conf の設定をする。

user  nginx;
worker_processes auto;

error_log   /var/log/nginx/error.log warn;
pid         /var/run/nginx.pid;

worker_rlimit_nofile 150000;

events {
    worker_connections  65535;
    multi_accept on;
    use epoll;
}

http {
    server_tokens off;
    sendfile off;
    etag off;
    if_modified_since off;

    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 10;

    include /etc/nginx/mime.types;
    default_type text/html;
    charset UTF-8;

    access_log  /var/log/nginx/access.log;
    types_hash_max_size 2048;
    server_names_hash_bucket_size 64;

    gzip on;
    gzip_http_version 1.0;
    gzip_disable "msie6";
    gzip_proxied any;
    gzip_min_length 1024;
    gzip_comp_level 6;
    gzip_types      text/plain text/css application/javascript application/x-javascript
                    text/xml application/atom+xml application/xml+rss application/xml
                    application/json text/json text/javascript+json text/javascript ;

    server {
        listen 80 default;
        root /app/webroot;

        location / {
            try_files $uri /index.php?$args;
        }

        location ~ \.php$ {
            try_files $uri =404;
            include /etc/nginx/fastcgi_params;
            fastcgi_pass app:9000;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        }
    }
}

ここまででDocker周りの準備は完了。 さっそくビルドと起動をしてみる。

docker-compose build
docker-compose up -d

確認

http://localhost:8001 にアクセスして下の画像のように各種状態が緑になっていたら問題なし。

f:id:riosu:20191123130458p:plain

Riot Gmaes Ecosystem Analyticsについて

RiotAPIを使っているサービスは、11月26日まで Riot Games Ecosystem Analytics (RGEA) に対応しなければならない。 そのようなメールが来ていたのでまとめてみる。

Riot Games Ecosystem Analyticsとは

www.riotgames.com

ざっくり言うと、プレイヤーがサードパーティ製品をどのように使用しているかどうかをRiotが計測するためにAnalytics用のコードをサイトに突っ込んでくれという話であるようだ。 メールでそのスニペットが送られてくるので、それを受け取ったら30日以内に埋め込んで連絡してくれとある。 これをしない場合は、アクセスができなくなるらしい。 もしメールが来ていない場合は developer.riotgames.com で自身のメアドが正しいことを確認する必要がある。

実際の設定について

メールで送られてきたスニペットは上記のサイトのサンプルとほぼ同じであった。

<script type="text/javascript">
    (function(t,l){
        let w=window,d=document,s=d.createElement('script'),f=d.getElementsByTagName('script')[0];
        w[t]=w[t]||function(){(w["_rgea"]=w["_rgea"]||[["uts",new Date()]]).push(Array.prototype.slice.call(arguments));w.RGEA&&w.RGEA.p();};
        s.type='text/javascript'; s.async=true; s.defer=true; s.src=l; f.parentNode.insertBefore(s,f);
    })("rgea", "https://static.developer.riotgames.com/js/rgea.min.js");

    rgea("propertyId", "RGEA****-****");
    rgea("uid", "puuid"); // assuming the user is verified; encrypted puuid
    rgea("lolpid", "platformId"); // league platform id (na1, la2, euw1, kr, etc)
    rgea("lolaid", "accountId"); // assuming the user is verified; encrypted account id
    rgea("anonymous", false); // defaults to true
</script>

uidlolaid はそのページを開いているユーザとLoLアカウントが紐付いている場合に指定するようだ。 uid には puuid (Player Universally Unique Identifiers)lolaidaccountId を渡す。 ユーザと紐ついていない場合のアクセスの場合 anonymousetrue を入れれば良さそう。

GCS(Google Cloud Storage)にファイルをバックアップする(最新N個のみ保持)

例えば運営しているサービスのデータベースのダンプをデータベースサーバで毎日とっているとして、 そのダンプファイルをどこか別のところにコピーしておかないと、結局サーバごと死んだときに復旧できなくなる。

適当なバックアップ用のサーバを用意して、そこにrsyncやscpしても良いのだが、 バックアップ用サーバの運用もめんどくさいので、今回はGoogleクラウドストレージであるGoogle Cloud Storage(GCS)にバックアップ設定を行う。

この際、常に新しいバックアップファイルを別の名前でアップすると課金額が増えてしまうし、 逆に毎日同じファイル名で上書き保存していくと、数日前のものに戻したいという時に対応できない。 (そもそもそんな要求があるかは知らないが)

そこで、GCSのバージョニング機能と、ライフサイクル管理の機能を使う。 まず、バージョニング機能を有効にする。この操作はGUIからは現時点では行えないため、 gsutil コマンドを利用する。 gsutil コマンドのインストールは色々なサイトで解説されているので、ここでは説明しない。 (aptで無邪気に apt install gsutil とか打つと、全然関係ないツールが落ちてくるので注意)

バージョニングを有効にする

バージョニングはBucket単位で有効/無効の設定を行う。

gsutil versioning set on gs://<bucket_name>

ちなみに現在の設定は以下で確認できる

gsutil versioning get gs://<bucket_name>

ライフサイクル管理の設定を行う

バージョニングが有効の場合、ライフサイクル管理の設定がGUIから行えないので、 こちらも gsutil コマンドを利用する。

まずはライフサイクルを定義したJSONファイルを作成する 今回は、新しい3バージョンのみを保持する設定を行った。詳細なオプションは下記のページで確認できる

Buckets  |  Cloud Storage  |  Google Cloud

$ cat gcs_lifecyscle.json
{
    "lifecycle": {
        "rule": [
            {
                "action": {"type": "Delete"},
                "condition": {
                    "numNewerVersions": 3
                }
            }
        ]
    }
}

ここまで来たら、あとはgsutilを打つだけ

$ gsutil lifecycle set gcs_lifecyscle.json gs://<bucket_name>

ライフサイクルの反映はGoogleのドキュメントによると、反映に最大で24時間かかるとのことなので、気長にまとう。

Managing Object Lifecycles  |  Cloud Storage  |  Google Cloud

ファイルのアップロード

アップロードは下記のように簡単に行える。

gsutil cp backup/hoge.sql gs://<bucket_name>/backup.sql

ファイルの確認

バージョニングされたファイルを含めて見たいときはGUIから見れないようなので下記のように -a をつけた gsutil ls コマンドで確認する

gsutil ls -a gs://<bucket_name>/db/backup.sql

さらに詳細な情報(アップロードされた日付など)が見たい場合は、オプションに -l を追加すれば良い。 (ちなみに、もっと詳細な情報は -L で確認できる)

gsutil ls -la gs://<bucket_name>/db/backup.sql

ConEmu+WSL環境でvimの矢印キーが使えない場合

ConEmuを使ってWindows Subsystem for Linuxを操作しているのだが、 vimを使うときに矢印キーが使えない現象が発生したのでメモ

conemu.github.io

公式の解決法としては、wslbridgeを経由する形で実行するようにタスクを作って、それを使えという感じのようだが、 この方法だと、私の環境だとうまくzshを利用することができなかった(文字化けが発生したりした)

なにか別の方法はないかなと、いろいろぐぐってみたのだが、vim側の設定を変えることでうまくいったのでメモ

 set term=builtin_ansi

tigで日本語が文字化けする

WSL(Windows Subsystem for Linxu)/Bash on Windowsにて開発環境を整えているときに、 aptで入れた tig で日本語が文字化けする現象にあたったのでメモ

mfham.hatenablog.com

ncursesのビルドは基本的に上記の手順通りにやればよかった。 私の環境だと configure: error: no acceptable cc found in $PATH とか ./configure: 18395: ./configure: make: not found とか言われたので、適当に必要なものをインストールした。

sudo apt-get install build-essential

ncursesのインストールが完了したあと、今度はtigをビルドした。

wget https://github.com/jonas/tig/releases/download/tig-2.4.1/tig-2.4.1.tar.gz
tar xvzf tig-2.4.1.tar.gz
cd tig-2.4.1
./configure
make
sudo make install

これでまだ文字化けしていたので、下記の設定を.zshrc(または.bashrcとか)に入れたらうまくいった

export LC_ALL=en_US.UTF-8

WSL(Windows Subsystem for Linux/Bash on Windows)でvagrant sshができない問題とその対処

WSLで vagrant ssh しようとしたら Permission denied (publickey). と言われてログインできない事象に遭遇したので対処した。

まず、vagrant ssh 時に実行されるSSHコマンドを自分で書いて、どういうエラーなのかを確認したところ、下記のようになっていた。

% ssh vagrant@127.0.0.1 -p2222 -i .vagrant/machines/default/virtualbox/private_key
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0777 for '.vagrant/machines/default/virtualbox/private_key' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key ".vagrant/machines/default/virtualbox/private_key": bad permissions
Permission denied (publickey).

VMの鍵である .vagrant/machines/default/virtualbox/private_keyパーミッションが0777になっているため繋げられないとのことであった。 というのも、今回Vagrantを導入したリポジトリWindowsファイルシステム上( /mnt/c 配下 )にしているため、パーミッションがデフォルトではすべて0777になる。 結果として、生成された private_key も0777になり、SSHクライアントに怒られる。

解決策

最近のWSLではWindowsファイルシステムについてもパーミッションを自由に設定できるようになったらしい。 ただしデフォルトではその機能が有効になっていないので、有効にする必要がある。

まず、 /etc/wsl.conf を下記の内容で作成する

[automount]
options = "metadata"

設定後WSLを再起動する必要がある。 Windowsを起動させたまま再起動させるのはめんどくさそうだったので、普通にPCごとリブートさせた。

再起動後、private_keyのパーミッションを0600にしたら完了。普通に vagrant ssh できるようになった。

$ chmod 600 .vagrant/machines/default/virtualbox/private_key
$ vagrant up
$ vagrant ssh