窓辺通知 over SSH  with WSL & Powershell

既出じゃないことを祈る．
リハビリ更新です．

## TL;DR

- リモートのLinuxサーバーで作業終わったら，Windowsでアラームしたい．
- どうも Powershellの `New-BurntToastNotification` というものをつかえばいいらしい．
- WSLからPowershell叩けばいいよね．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209562/e4df7e7c-6c02-5919-584c-2af3f24566ee.png)


```mermaid
sequenceDiagram
    autonumber
    participant Powershell
    participant WSL
    participant Server
    WSL ->> WSL:  Netcat: Listen localhost:47123
    WSL ->> Server: SSH with Reverse from 47123 to localhost:47123
    Server ->> Server: Netcat: Send message and options to localhost:47123
    Server -->> WSL: Forwarding remote:47123 to me:47123
    Note right of WSL: port 47123で傾聴中
    WSL -->> Powershell: New-BurntToastNotification 
    Note right of Powershell: つうち！
```

### 必要なもの

- Windows(検証: Windows 11 21H2 220000.652)
  - Powershell version 5（以上？）（検証: 5.1.22000.652）
  - WSL(検証: Ubuntu 20.04.4 LTS/WSL2 Kernel 5.10.102.1)
    - ssh, netcat, sh
- Linux鯖(検証: Ubuntu 18.04 LTS）
  - sshd, netcat, sh

## New-BurntToastNotificationとやらについて

https://qiita.com/The-town/items/60c539ad0fef77ee60ba

### TL;DR

```powershell
# Policy変えて 
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
# インスコ
Install-Module -Name BurntToast
# 一応（？）いんぽーと
Import-Module BurntToast
# 実行テスト
New-BurntToastNotification -Text hoge
# -Sound Call[|2-10], -Sound Alarm[|2-10]で着信やアラーム音できる．
New-BurntToastNotification -Text piyo -Sound Alarm
# Logoアイコンもいける
New-BurntToastNotification -Text hoge -AppLogo C:\path\to\logo.png
```
WSLからテスト
```bash
powershell.exe "New-BurntToastNotification -Text fuga"
```

## テスト

nc越し

```shell-session
$ powershell.exe "New-BurntToastNotification $(nc -w1 -lvp47123 -s"127.0.0.1" >& /dev/null)" &
$ lsof -i:47123 # check listening
$ echo "-Text fuga" | nc 192.0.2.1 47123 -w1 # failed(no localhost address)
$ echo "-Text hoge" | nc localhost 47123 # success
```

ssh越し

```shell-session
user@local:~$ powershell.exe "New-BurntToastNotification $(nc -w1 -lvp47123 -s"127.0.0.1" >& /dev/null)" &
user@local:~$ ssh -R 47123:127.0.0.1:47123 remote
user@remote:~$ echo "-Text hoge" | nc localhost 47123
```

## シェルスクリプト書く．

```
source below_shellscript.sh
```

```zsh
# 47123ポートでやりとり
export WSL_NOTIF_PORT="47123"

# WSLだったら....
if [[ -v WSL_DISTRO_NAME ]];then
# WSL通知待ちを定義
_waitWslNotification() {
  WSL_NOTIF_LOGO_PATH=" "
  # lsb-release呼んで Ubuntuなら，WSLのdotfilesの中に突っ込んであるubuntu.pngを抜く．
  # ほかの奴はTux君（linux.png）表示．
  source /etc/lsb-release
  case "$DISTRIB_ID" in
    "Ubuntu") logo="-AppLogo $(wslpath -w "$HOME/.zsh/assets/logos/ubuntu.png")" ;;
    *) logo="-AppLogo $(wslpath -w "$HOME/.zsh/assets/logos/linux.png")" ;;
  esac
  # 
  while true; do
    recv="$(nc -w1 -lvp"$WSL_NOTIF_PORT" -s"127.0.0.1")"
　　# 受信データあれば通知！　
    [[ "$recv" != "" ]] && powershell.exe "New-BurntToastNotification $logo $recv"
  done
}
# 47123ポート使ってなかったら，実行する．
if ! (lsof -i:"$WSL_NOTIF_PORT" > /dev/null 2>&1);then
# &!で裏で実行かつシェル閉じても実行し続けるようにしとく（デーモンでよくないか？）
_waitWslNotification &! >&/dev/null
fi
fi
# nc投げるだけのWrapper関数
## help text
_wslnotify_help(){
cat << USAGE
Usage: wslnotify [-a AlarmNumber(1-10) | -c CallNumber(1-10)] [-t text]

Options:
 -a | --alarm [number]: ring alarm
 -c | --call  [number]: ring alarm
 -t | --text  [text]: notify text
 -v | --verbose: verbose mode
 -h | --help: help text(this)
USAGE
}

## main
wslnotify() {
  text=""
  sound=""
  verbose=1
  # check args
  OPTS=$(getopt -o hva:c:t: -l help,verbose,alarm:,call:,text: -- "$@")
  p=$?
  if  ! $(return $p) || [[ $# -eq 0 ]]; then
    _wslnotify_help
    return 1
  fi
  # rotate args
  eval set -- "$OPTS"
  while true; do
    case $1 in
      -h | --help)
        _wslnotify_help
        return 0
        ;;
      -v | --verbose)
        verbose=0
        shift
      ;;

      -a | --alarm)
        if [[ $2 -lt 1 ]] || [[ $2 -gt 10 ]];then
          echo "Error: Invalid Alarm Number: $2"
          return 1
        fi
        if [[ $2 -lt 1 ]] || [[ $2 -gt 10 ]];then
          echo "Error: Invalid Call Number: $2"
          return 1
        fi
        sound="-Sound Alarm$2"
        [[ $2 -eq 1 ]] && sound="-Sound Alarm"
        shift 2
        ;;
      -c | --call)
        sound="-Sound Call$2"
        [[ $2 -eq 1 ]] && sound="-Sound Call"
        shift 2
        ;;
      -t | --text)
        text="-Text $2"
        shift 2
        ;;
      --)
        shift
        break
        ;;
      *)
        echo "Error: getopts error." >&2
        return 1
        ;;
    esac
  done
  # verbose mode
  if $(return $verbose);then
    echo "VERBOSE: Send to \"$sound $text\""
  fi
  # sending
  echo "$sound $text" | nc -w1 127.0.0.1 "$WSL_NOTIF_PORT"
}

# sshするときにプロキシ張る
alias ssh='ssh -R ${WSL_NOTIF_PORT}:localhost:${WSL_NOTIF_PORT}'
```

### TODO:

- Socket用意してる状態で何回も`ssh -R 47123:localhost::47123` していいのか？
  - Socketファイルの有る無しで挙動変えさせる？ `alias ssh` --> `function ssh()`
- `nc`に依存せずに，`/dev/tcp/localhost/47123`だっけ？，を使えないか？
- 47123を開けっ放しにできないか？（`-k`使えたっけ）
- `wslnotify`じゃなくて`winotify`のほうがいいかも

## End of Article

- WSLを用いたSSH接続状態におけるクライアントWindows通知の実装した．
- `tmux`上で長い処理をしてたけど，いつ終わるのかをSlackとかにWebhookで送ってたけど，よりシンプルに通知できた．
- 今後はSlack webhookで用いてたプロセス監視スクリプトと合体し，処理が終わったら自動的に通知するようにしたりを検討する．
- また，`tmux`の子プロセスでフォアグラウンドで動くプロセスで，一定時間以上走っているものがQUITしたら自動的に通知とか，ひまがあればやってみる．