おかざきPの記録

Mac, QNAPの設定やプロデューサー業の記録

スピーカーとアンプのインピーダンスの組み合わせ

結論

  1. インピーダンスを気にする必要があるのは真空管アンプ
  2. 半導体アンプであれば常識的な使用範囲ではインピーダンスを気にする必要はない
  3. スピーカーとアンプのどちらに安全マージンを取るかで議論の内容が逆転する

背景

 スピーカーを更新しようかと思って色々と調べてると「インピーダンスがー」という話をチラホラと見かけた. 「発電所がー」のレベルであれば無視していいのだが,最悪スピーカーかアンプが故障するというのだから無視できない. そんな重要な問題にも関わらずオームの法則を使った中途半端な記事や, 逆に詳細すぎて結論としてどうしたらいいのか判然としない記事に多くぶつかったため,自分で問題を整理しようと思った.

カタログスペック

 実例を元に故障しない使用範囲を計算する. 故障するかを考えるだけならインピーダンスの周波数特性など必要ないため, インピーダンスを直流抵抗として扱う. こうなるとほぼ中学校理科のレベルで議論ができる.

スピーカー

 私が気になっているものと保有しているもののヤマハ製品を例に見てみる.

型番 インピーダンス 許容入力 最大入力 能率@1m 構成
NS-F901 6 ohm 50 W 200 W 89dB/2.83V フロアスタンディング
NS-B750 6 ohm 30 W 120 W 87dB/2.83V ブックシェルフ
NS-C500 6 ohm 40 W 160 W 87dB/2.83V センター

 これらが壊れるのは許容入力を常時超える電力もしくは,最大入力を瞬間的に超える電力が入力された場合になる. ではスピーカーに電力が入力される状況は?と問われれば当然,音を出す時である. すなわちスピーカーが無事かという問題はどこまでの音量 = 電力を入力してもいいのかという問題になる.

 電力と音量の関係を能率を例に計算してみる. 今回のスピーカーは全て6 ohmのため,能率で示されている2.83 Vは W = E^2 /Rから1.33 Wに相当する. つまり,

許容入力>30 Wに対してたった1.3 Wで87dBもの音量

が得られる. dBの値と実際の音はデノンのブログの下部の表が一番わかりやすいのだが, 87dBなどというのは家庭で聞く分には非常に大きな音に分類される. ということで近所迷惑にならない範囲で鳴らしていれば,音量由来でスピーカーが壊れることはまずない. そんな簡単に壊れてたらクレームの嵐で商売にならないのだから当然だろう.

 参考として許容入力 W_{max}をスピーカーに投入したらどうなるかを計算しておこう, まず音の大きさ Sを音のエネルギー Wで表す.  Sと音の振幅 Pの関係は音量の単位dBの定義に従うと以下のように変換できる.


\begin{align}
S &= 20 log(P/P_0) \\
&= 10 log(P^2/P_0^2)\\
&= 10 log(W/W_0)
\end{align}

 ここで P_0音の基準音圧 W_0 P_0のエネルギーを表す. 能率で求めた1.33 W以上の投入電力が全て音に変換されると仮定する. 能率を S_1とすれば W_{max}を入力した時の音量は次のようになる.


\begin{align}
S_{max} - S_1 &= 10 log(W_{max}/W_0) - 10 log(W_1/W_0)\\
&= 10 log(W_{max}/W_1)\\
S_{max} &= S_1 + 10 log(W_{max}/W_1)\\
&= S_1 + 10log(W_{max}/1.33 W)
\end{align}

 許容入力の最も小さいNS-B750のスペックを代入した時の音量は87dB + 10 log(30/1.33) = 100dBとなる. これはデノンによればライブハウスやカラオケの音量に相当する. こんなものを常用していればスピーカーの前に自分の耳やご近所関係が壊れる.

アンプ

 同様にアンプも確認する.私はAVアンプを使用しているため,ヤマハのAVアンプを例にとる.

型番 インピーダンス 定格出力 定格出力時の電圧 実用最大出力 ch数
RX-S601 6 ohm 60 W/ch 19.0 V 125 W 5
RX-V4A 6 ohm 80 W/ch 21.9 V 145 W 5
RX-A2A 8 ohm 100 W/ch 28.3 V 165 W 7

 上で計算した通りスピーカーは1.33 Wもあれば大音量で鳴るため,アンプを定格出力で稼働させることは通常はない. 誤って音量最大 = 定格出力にしてしまっても,上2つはインピーダンスが一致しているため明らかにスピーカーの最大入力以下に収まる.

 では残るRX-A2Aを計算してみよう.定格出力時の電圧は28.3 Vになる.これを6 ohmのスピーカーにかけると W = E^2/Rより133 Wと計算される. 残念ながらNS-B750の最大入力120 Wを超えてしまうため,間違ってボリュームを最大に振り切ることがないように注意が必要となる. さらには定格出力100 Wのつもりがスピーカー側のインピーダンスが低いために,想定以上の出力133 W (< 実用最大出力165 W)をしてしまっている.

音源のクリップ問題

 音源の問題でスピーカーが破損することはあるらしい. マスタリングで限界まで詰め込まれた音源とかが危険なのかもしれない.

真空管アンプ

 上で計算したのはあくまでスピーカーと半導体アンプの組み合わせで故障するかを議論しただけ. 真空管アンプの性能を引き出すには色々と考えるよう.

ImageJとpythonで粒子解析を自動化する

追記(2022/06/26)

 自動化の難易度が高い画像を手動で分析する方法も投稿しました.

やったこと

 ImageJを用いて粒子形状を自動で解析する初歩的な内容はこちらの方がまとめられている. ただ個人的には改善したい点もたくさんあって,

  1. もっとオブジェクト指向にしてコードの見通しを良くしたい
  2. RoiManagerが表示されてると実行速度が絶望的に遅い
  3. 同じくIJ.run()は裏でGUIを操作しているため速くない
  4. 長さとか面積とかに単位が欲しい
  5. ParticleAnalyzerのオプションを色々弄りたい
  6. 元画像のどこが粒子として認識されたか確認したい

とか色々ある. このため前述の巨人の肩に乗りながら(コードを流用しながら),上記の要望を叶える方法を調べてコードを改変した. なお少なくともImagePlusImageProcessorの関係と使い方は理解していないと本コードを理解するのは苦労するため, 拙著を流し読みした方が良いかもしれない.

改変したpythonコード

import os

from ij import IJ
from ij.process import ImageProcessor
from ij.plugin import Colors
from ij.plugin.frame import RoiManager
from ij.plugin.filter import ParticleAnalyzer, BackgroundSubtracter, EDM
from ij.measure import ResultsTable, Measurements



def main(imagefilepath):
    # 画像を開いてImagePlusを取得する
    imp = IJ.openImage(imagefilepath)

    # 1ピクセルが相当する実世界の長さを設定
    # ImagePlusから該当するmetaデータを取り出して編集する
    cal = imp.getCalibration()
    cal.pixelHeight = 0.1
    cal.pixelWidth = 0.1
    cal.setUnit('um')

    # 忘れずにImagePlusに反映する
    cal.setImage(imp)


    # 背景を均一にする
    bs = BackgroundSubtracter()
    ip = imp.getProcessor()

    # 後でimp.setProcessor()すると戻せなくなるため
    # 無編集のデータも先に分けておく
    ip0 = imp.getProcessor().duplicate()

    # 背景の差引
    # bs.rollingBallBackground(ColorProcessor, radius, createBackground, lightBackground, useParaboloid, doPresmooth, correctCorners)
    bs.rollingBallBackground(ip, 40.0, False, True, False, True, True)


    # 粒界を定める閾値を設定する
    # 第一引数で2値化のアルゴリズムを選択できる
    # ip.setAutoThreshold(method, darkBackground, lutUpdate)
    ip.setAutoThreshold('Default', False, ImageProcessor.NO_LUT_UPDATE)

    # 2値化した画像を取得
    mask = ip.createMask()

    # 今回は粒界が離れているため省略
    #EDM().toWatershed(mask)

    # 2値化していてもParticleAnalyzerのために閾値の設定は必要
    mask.setAutoThreshold('Default', True, ImageProcessor.NO_LUT_UPDATE)


    # 粒子解析

    # 粒子の輪郭をROIで保存する
    # 引数にTrueかFalseを与えるとウィンドウが表示されなくなって高速化できる
    # どちらを与えても同じで,どちらも与えないと表示される
    rm = RoiManager(False)

    # 解析結果の保存先
    # こちらは何もしなくてもウィンドウは表示されない
    rt = ResultsTable()


    # 粒子解析する本体

    # インスタンス化せずにクラスメソッドとして実行すると次のインスタンス化時に反映される
    ParticleAnalyzer.setRoiManager(rm)

    # インスタンス化
    # 第一引数は解析方法の指定を行なっており2進数の和で表現されている
    # ただそれぞれ覚えていられないため次のようにクラス変数を参照する
    # 第二引数も同様に数値化する項目を指定できるが全部解析して後で省けばよし
    # 第4,5,6,7引数は解析対象とするそれぞれ最小面積,最大面積,最小円形度,最大円形度
    # 工夫してノイズを除外すること
    pa = ParticleAnalyzer(
                ParticleAnalyzer.EXCLUDE_EDGE_PARTICLES
                + ParticleAnalyzer.INCLUDE_HOLES
                + ParticleAnalyzer.ADD_TO_MANAGER,
                Measurements.ALL_STATS,
                rt, 0., 1.e9,
                0., 1,
            )

    # 長さの情報をもつImagePlusと2値化画像のImageProcessorを渡す
    pa.analyze(imp, mask)


    # 保存
    savefilepath = imagefilepath[:-4]+"_"
    rt.saveAs(savefilepath + "Results.csv")
    imp.setProcessor(mask)
    IJ.saveAs(imp, "bmp", savefilepath + "masks.bmp")
    rm.runCommand("Save", savefilepath + "Roi.zip")


    # 抽出した粒子の輪郭を描画する

    # 輪郭を黄色にする
    # 使いたい色の16進数表記を変換してColorsオブジェクトにする
    c = Colors().decode('#ffff00')

    # そのままだとグレースケールの画像になっている
    # そこに色を渡してもグレースケールに変換されてしまうため
    # 元の画像をRGBカラーに変換しておく
    ip = ip0.convertToColorProcessor()

    # やっと色を指定できる
    ip.setColor(c)

    # ROIそれぞれを描画するとImageProcessorに渡した色が使われる
    for roi in rm.getRoisAsArray():
        ip.draw(roi)

    # ImagePlusに画像を反映して保存
    imp.setProcessor(ip)
    IJ.saveAs(imp, "jpg", savefilepath + "AnalyzedResult.jpg")

    # close roi manager, results table and images
    rm.close()
    rt.reset()
    imp.close()

 フォルダ内の画像に一括適用したいなら この関数をos.walkする. ip.setAutoThreshold()の第一引数は文字列で 他のアルゴリズムを与えられる. 解析した粒子を明示するのは重要で,自動化していると想定外な輪郭を取得される可能性もあり,それを簡単に確認できる必要がある. 初めて扱う種類の画像であればip.setAutoThreshold()の第一引数を全て試して最適なアルゴリズムを選択するべきだろう.
 なおRoiManagerを使っているとheadlessモードでエラーが出てRoiManagerインスタンス化に失敗する.このため本コードはImageJのエディタから実行する必要がある.

ImageJ Fijiとpythonでフォルダ内の画像を一括処理

ImageJを使う理由

 ImageJ Fijipython(Jython)を使って自動で画像処理する方法を調べたので,何段階かに分けて投稿していく. pythonならopencvとかあるのになんでImageJを選んだかといえば, 材料配合が主体の職場ではpythonの環境構築すらハードルが高く,部内配布が事実上不可能だから・・・. ImageJなら最悪GUIでtry-and-errorを繰り返せば誰でもそれらしい結果が得られるのも,画像処理を検討する雰囲気の醸成に役立つはず.

この記事で説明する内容

 第一弾では画像の開き方と保存方法を確認した後,切り取り方法を確認する. その後,上記を関数にまとめてフォルダ内の画像に一括処理する方針で進める.
 なおGUIで頑張る方法はマウスポチポチしてればいいので一切説明しない. 光学顕微鏡とかSEMとかの画像が数十枚以上ある場合を想定. そもそもpythonの文法についても知っていることを前提.

pythonのコード

コードの書き方

 材料系の職場だと笑い事じゃなく,エディタがメモ帳しかない可能性が高い. 幸いにしてImageJにもエディタが付属しておりシンタックスハイライトくらいはできる. ImageJを開いて,File>New>Text windowもしくはFile>New>Script...で起動できる.
 なおこれで作業しているとデバッグは苦しい. persistentにチェックを入れてコードを実行すると,実行完了時の環境から抜けずに止まる. その状態でインタラクティブシェルとして変数を調査することになる. ここではpdbは機能しないため覚悟すること. pdbでのデバッグが必要であればコマンドラインからheadlessモードにて実行する必要がある(後述).

画像の開きと保存(と閉じ)

from ij import IJ
from ij.io import Opener


if __name__=='__main__':
    '''詳しいパラメータ等は下記の公式APIを参照.
    https://imagej.nih.gov/ij/developer/api/ij/ij/io/Opener.html
    '''

    # 画像のパスを設定.絶対パスでなければエラー.
    # 今回はImageJに付属のサンプル画像をデスクトップに保存して利用.
    src = '/Users/OkazakiP/Desktop/AuPbSn40.jpg'

    # 画像を開く.
    imp = Opener().openImage(src)

    # 他の開き方.結果は同じ.
    imp = IJ.openImage(src)

    
    # 何も変更せずに保存.IJクラスは色々な便利メソッドの塊.
    IJ.saveAs(imp, 'jpg', '/Users/OkazakiP/Desktop/AuPbSn40_.jpg')

    # 使い終わったら片付ける.
    imp.close()

    # 作業完了の報告.
    print 'Finish.'

 難しいことはないでしょう. 私のテスト環境がmacだからパス文字列が/で区切られてるけど,windowsパスでも動くはず. この程度でif __nam__=='__main__':で始める意味はないが, 最後にこの部分(の派生系)を関数にまとめるため,インデントがあると微妙に楽.
 Openerの他にFileOpenerFileSaverなるクラスがあるが, これらは事前にFileInfoというクラスで画像の情報を与える必要があるため面倒そう. IJだけ覚えれば十分だから理解するの諦めた.
 スクリプトの最後には画像を明示的に閉じないと,backgroundで開きっぱなしになってしまうから注意.

画像の切り取り

from ij import IJ
from ij.io import Opener


if __name__=='__main__':
    '''ImageProcessorに対して作業をしていく.
    https://imagej.nih.gov/ij/developer/api/ij/ij/process/ImageProcessor.html
    '''

    # 先ほどと同じく画像を開く.開くとImagePlusオブジェクトが返される.
    src = '/Users/OkazakiP/Desktop/AuPbSn40.jpg'
    imp = Opener().openImage(src)

    # ImagePlusが持つImageProcessorを取得する.
    ip = imp.getProcessor()
    
    # ImageProcessorに対して切り取り領域を指定.
    # 画像の原点は左上.
    # ip.setRoi(int x, int y, int width, int height)
    ip.setRoi(0, 0, 200, 200)

    # 切り取り.切り取り後のImageProcessorが返される.
    ip_ = ip.crop()

    # 切り取りの結果をImagePlusに反映する.
    imp.setProcessor(ip_)

    # 保存.
    IJ.saveAs(imp, 'jpg', '/Users/OkazakiP/Desktop/AuPbSn40_cropped1.jpg')


    # 違う位置で切り取り.
    ip.setRoi(200, 200, 200, 200)
    ip_ = ip.crop()
    imp.setProcessor(ip_)
    IJ.saveAs(imp, 'jpg', '/Users/OkazakiP/Desktop/AuPbSn40_cropped2.jpg')


    # 最初に取得したImageProcessorには何も変更がなされていない.
    imp.setProcessor(ip)
    IJ.saveAs(imp, 'jpg', '/Users/OkazakiP/Desktop/AuPbSn40_cropped0.jpg')


    imp.close()
    print 'Finish.'

 突如現れるImagePlusImageProcessor 画像を開いた時に返されるのがImagePlusImageProcessorはその中に含まれる. ImageProcessorは画素値を含む,いわゆる画像そのもの. これに加えて画像のパスとか,1ピクセルあたり何umとかのmeta情報を合わせるとImagePlusになる.
 このため画像の切り取りは画素を相手にするためImageProcessorを抜き取って操作する. 操作後の画像を保存するには上の画像を開く時に諦めたFileInfoが必要となるため, それを保持するImagePlusに画素の情報を戻してから保存する.

フォルダ内の画像を一括処理

import os

from ij import IJ
from ij.io import Opener


# 切り取りと保存を行う関数
def crop_image(src, output, x=0, y=0, w=200, h=200):
    imp = Opener().openImage(src)
    ip = imp.getProcessor()
    ip.setRoi(x, y, w, h)
    ip_ = ip.crop()
    imp.setProcessor(ip_)
    IJ.saveAs(imp, 'jpg', output)
    imp.close()


if __name__=='__main__':
    folder = '/Users/OkazakiP/Desktop/Images'

    # 保存用のフォルダを同じ階層のConverted以下に作る
    parent = os.path.dirname(folder)
    result = os.path.join(parent, 'Converted')
    try:
        os.mkdir(result)
    except OSError:
        _result = result
        # 11回までは新しく作る
        for i in range(10):
            result = _result + str(i)
            try:
                os.mkdir(result)
            except OSError:
                continue
            break

    for root, dirnames, filenames in os.walk(folder):
        folder_name = os.path.basename(root)
        root_ = result + root[len(folder):]
        try:
            os.mkdir(root_)
        except:
            pass
        for filename in filenames:
            src = os.path.join(root, filename)
            output = os.path.join(root_, filename)
            # 画像以外が含まれてエラーを吐くことへの対策
            try:
                crop_image(src, output)
            except:
                print 'Error on %s' % (src)

    print 'Finish.'

 もはやImageJに特有の事項はなし.普通にos.walkで関数を回すだけ. 自動処理の目的は果たせるけど,フォルダ選択はスクリプトを書き換える必要があってuser friendlyでない. そこでフォルダ選択用のGUIをくっつける.

GUIでフォルダを選択する

import os

from ij import IJ
from ij.io import Opener, DirectoryChooser

...
...

if __name__=='__main__':
    chooser = DirectoryChooser('一括処理したい最上位のフォルダを選択して下さい.')
    folder = chooser.getDirectory()

    # 次の行は誤植ではない.パスの末尾に区切り文字が付与されてしまうため必要.
    folder = os.path.dirname(folder)

    # 保存用のフォルダを同じ階層のConverted以下に作る
    parent = os.path.dirname(folder)
    result = os.path.join(parent, 'Converted')

...
...

    print 'Finish.'

 変更がない部分は省略した.DirectoryChooserを使うとGUIでフォルダが選択できる. ただし末尾に区切り文字が追加されるため,そのままだと先のコードと挙動が変わってしまう. これを避けるために一行追加している.
 ばら撒くにはこれでいいけどさ?自分で使う分にはマウスポチポチも無駄じゃん?どうせなら全部コマンドで済むと楽じゃん?

フォルダをコマンドライン引数で受け付ける(headlessモード)

#@String folder
import os

from ij import IJ
from ij.io import Opener

...
...

if __name__=='__main__':
    # もはや不要
    #folder = '/Users/OkazakiP/Desktop/Images'

    # 保存用のフォルダを同じ階層のConverted以下に作る
    parent = os.path.dirname(folder)
    result = os.path.join(parent, 'Converted')

...
...

    print 'Finish.'

 一行目に追加した#@で始まる分はScript parameterとかいうらしい. こうするとコマンドラインから引数を渡せるようになる.ImageJはpython2系でargparseがないから助かる. sys.argv?知らん.使ったことない.
 さてこのスクリプトコマンドラインからheadlessモードで実行するには次のようにする. ここはOSによって異なるため公式のガイドを参照すること

>> cd Fiji.app/Contents/MacOS
>> ./ImageJ-macos --ij2 --headless --console --run test_walk_parameter.py 'folder="/Users/OkazakiP/Desktop/Images"'

 今回は追加しなかったが,関数crop_imageに渡す引数を全て#@int xなどとしてコマンドラインから渡すことも可能. headlessという割には起動時のヘッドが激重.このheadlessモードであればpdbを用いてのデバッグも普通に実行できる.
 なおheadlessモードでは使えない機能も存在するため注意.要するにGUIに依存するタイプ. ウィンドウ非表示ならいけるか?と思ったらそんなことなかったからタチ悪い.

所感

 ImageJ APIが死ぬほど分かりにくい.pythonの標準モジュールや,デファクトスタンダード化してるモジュールのリファレンスは本当に関心する. ただしmatplotlib,お前はダメだ.

windowsにOpenSSHサーバを立てた

やったこと

  • windows10の標準機能となったOpenSSHの有効化
  • Windows Subsystem for Linux(WSL)のOpenSSHも使えるようにした
  • VNCMaciPadからwindowsの画面を表示できるようにした

Jupyterサーバとして動く!  ……はず!

環境

ことの始まり

VRゲームに興味があって 比較的上位なGPUを積んだwindows機を購入した.
よくよく考えてみればまともなGPUがあるということは
ディープラーニングも捗ると気付いたため,
windowsにjupyterサーバを立てることにした.
ただ私はmacに慣れきっていてwindowsゴミUIが操作しにくいため,
windowsのjupyterサーバにMacからデータを投げる方針とした.

実作業

PythonやTensorflow,Cudaの導入は特に難しいことはないため省略.
特にTensorflowのドキュメントが非常に分かりやすく素晴らしかった.
microsoftも見習え.
この記事を書いている2020年2月現在,
KerasがPython3.6までにしか対応していない.
このKerasが便利そうなのでPythonのバージョンは3.6をインストールした.

標準のOpenSSH

インストール

一応microsoft分かりにくい公式ドキュメントに全部書いてある.
簡単で間違えようがない方法が分かりにくく平文で書いてあって,
おそらくPowershellを推すために,
とっつきにくい方法をダラダラと丁寧に書いてある.
GUI経由でwindowsの設定からインストールしてしまうのが吉.

初期設定

Powershellでのインストールは何をしているか少し分かりにくい一方で,
ファイアウォールや常駐サービス化の初期設定はPowershellの方が確実.
これら設定をGUIでポチポチ探していくのは私には辛かった…….
ドキュメントのコピペで放っておくとポート22を使用することになってしまうため,
sshd_configでポートを弄った後にでも他の設定を追加しておく.

詳細設定

何故公式はここまでしか載せていないのか.
SSH入れたらまずはsshd_config弄ってポート変更とパスワード認証の禁止をするでしょ.
そしてsshd_configの場所は公式には見当たらない.
というか日本語でこの点を明記してあるページもここ以外見つからない.
こちらの情報をありがたく拝謁すれば,
C:\ProgramData\ssh\sshd_configにあると分かった.
そこで以下の設定を管理者権限で編集する.

  1. #Port 22Port "他と被らないポート"
  2. #PermitRootLogin without-passwordPermitRootLogin forced-commands-only
  3. #MaxAuthTries 6MaxAuthTries 1
  4. #MaxSessions 10MaxAuthSessions 2
  5. #PubkeyAuthentication yesPubkeyAuthentication yes
  6. #PermitEmptyPasswords noPermitEmptyPasswords no
  7. Match Group administratorsの下の AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
    AuthorizedKeysFile .ssh/authorized_keys

特に管理者ユーザで作業している場合,
最後を忘れると鍵認証方式での接続がいつまで経ってもできない
続いて公開鍵の設定.
C:\Users\USERNAME\.ssh\authorized_keysSSHクライアントの公開鍵を記載する.
上で管理者グループの公開鍵の設定を変えなかった場合はそちらにも記載する.
以上の設定をしてからサービスなりコマンドラインなりからOpenSSHを再起動する.
他の端末から鍵認証方式でSSH接続できることが確認できたら
パスワード認証を完全に排除する.
再びsshd_configに戻って
#PasswordAuthentication yesPasswordAuthentication no
としてパスワード認証を禁止してしまう.

WSLのOpenSSH

microsoft storeからUbuntuをインストールすれば後の設定はunix系そのもの.
……ではなかった
wslではパーミッションの基本設定がwindows準拠で通常のunixと異なるため,
ただのubuntuと思って設定するとSSH接続できない

windowsSSH接続してからwslなりubuntuなりbashなりで起動はできる)
wsl上のtmuxを使うつもりのため,一応直接wslに接続できるようにしておく.

パーミッションの設定

おとなしくこちらに従う.
パーミッション既定を変えるファイルにsudo service ssh restartも一緒に仕込んでおけば
windowsを再起動してもこのファイルを実行するだけでsshdパーミッションを戻せる.

OpenSSHの設定

パーミッションさえクリアしていればこのまま
起動が確認できたら/etc/ssh/sshd_config
~/.ssh/authorized_keysを編集してsshdを再起動.

VNCサーバ

Ultra VNCでも入れればよし.
iPadからの接続はJump Desktopが便利.
Macからはfinderで⌘+Kのサーバへ接続でvnc://windowsのip:5900とすれば
windowsのデスクトップが表示できるはず.
MacVNCクライアントをフルスクリーンにすると
magic trackpadのスワイプだけでMacwindowsを切り替えられて便利.

感想

SSHサーバを立てたいまともな人間はwindowsやwslで誤魔化さずに
最初からlinux機を準備しているんだなあと感じるほどまともな情報が見当たらなかった.
出勤中の業務と在宅時のゲーム以外でwindowsを触る機会が減って精神衛生的にいい.
microsoft機械翻訳&たらい回しのクソドキュメントを修正しろ.

MinimServerにVPN経由でリモートアクセスする

やったこと

  • QNAPのNAS 231PにMinimServer(DLNAサーバー)をインストールした
  • 外出先のリモート環境でもiPadからDLNAが使えるように設定した

DLNAサーバー立ち上げの手順

MinimServerのインストール

公式HPの通り。

  1. NASのWEBデスクトップにログイン
  2. App Centerを起動
  3. MinimServerを検索してインストールボタンを押す
  4. JREが未インストールの場合は自動でインストールされる
  5. 以前にMinimServer用にJRE_ARMをインストールした場合は削除する
  6. マイアプリでMinimServerの起動を確認する
  7. MinimServerの開くボタンを押す
  8. ライセンスを承認する

MinimServerの設定

  1. MinimServerの設定ページが開かれるので、音楽ファイルを保存しているフォルダの最上位を指定する。 保存先がmusicの場合、/share/musicと入力してUpdateボタンを押す。
    f:id:Yukiho_P:20191231164215j:plain
    MinimServer_web_config
  2. NASSSH接続する。adminである必要はない。
  3. cd /share/CACHEDEV1_DATA/.qpkg/MinimServer/binする。
    ただしCACHEDEV1_DATANASの構築状態によって異なるらしい。
    configファイルを直接編集しても反映されない上に、
    MinimServerの起動すらできなくなることがあるので絶対にやってはいけない。
  4. mscript -iでMinimServerのインタラクティブコンソールに入る
  5. prop excludePattern=@Recycle, 除外したいフォルダ名が含む文字列, 除外したいファイル名が含む文字列
  6. Enterを押してインタラクティブコンソールを抜ける
  7. vi ../data/minimserver.config
  8. minimserver.udn =以降の文字列をコピーする

リモートからMinimServerにアクセスする手順

通常、DLNAはローカルLAN内でしか機能しない。
これはDLNA通信の始まりがマルチキャストパケットであり、こいつが普通にはルータの壁を越えられないからだ。
VPN接続してようがリモートからのマルチキャストパケットはDLNAサーバに届かない。
ただしこれは自動でDLNAサーバと接続する場合の問題。自動がダメなら手動で接続してしまえば良し。
そんな芸当ができるiOSアプリとして、今回は8player Proを購入して使う。
昔はPlugPlayerなるアプリでもできたらしいが、2019年現在はApp Storeにない。

MinimServerと同じLAN内で作業できる場合

公式通り。英語を読む以上の苦労はない。

リモートからMinimServerを探さざるを得ない場合

本記事を書いてる今の私のような奇特な人間の道標。
帰省前にルータ交換して色々と設定変わったらリモートアクセスできなくなった。
mscriptを使う方法を知らず、直接configファイルを弄ったら起動すらしなくなって、
最終的に再インストールする羽目になる人間はそういないんじゃないか・・・。

  1. Settingsをタップ
    f:id:Yukiho_P:20191231182725j:plain
    Settings
  2. Remote Accessをタップ
    f:id:Yukiho_P:20191231182826j:plain
    Remote Access
  3. Addをタップ
  4. Nameは好きな名前を付ける。
    URLにhttp://NASのIPアドレス:9791/minimserver.udn以降の文字列/Upnp/device.xmlを入力
  5. Saveして✔︎

これでMinimServerにVPN経由でリモートアクセスできる。
上記のxmlファイルのURLさえ分かっていればMinimServerに限らずどんなDLNAサーバでも接続は可能なはず。
私のようにサーバ再インストールとかしなければ、
8player Proに片っ端からDLNAサーバを記憶させておけば出先でも困らない。

QNAP 231PにAdGuard Homeを導入した

環境

  • QNAP 231P

要約

  1. arm-x41用のqpkgをダウンロード
  2. インストール
  3. ナビに従って初期設定
  4. AdGuard Homeを停止
  5. QNAPにssh接続
  6. 各種ファイルを変更
  7. dnsmasqを再起動
  8. AdGuard Homeを起動

実作業

やったことはQNAPのフォーラム(英語)を 忠実に実行しただけ。なんだけどトップの通りではほぼ使えない。 なぜならデフォルトでAdGuard Home(以下、AGH)はポート16553への問い合わせに応答するが、私の知る限りDNSサーバのポートまで設定できるマシンはないから。一般的にDNSサーバはポート53で稼働している。このためマシンにDNSサーバのIPアドレスを指定すると、マシンは設定したIPアドレスのポート53に問い合わせる。ところがQNAPにインストールしたAGHはポート53以外で稼働しているため、マシンがQNAPのポート53に問い合わせても応答がないわけだ。
このポート問題に対応するためにフォーラムに従ってQNAPの設定ファイルを弄った。ページ中程、2018年10月21日の回答によるとQNAPでデフォルトでポート53を使用しているdnsmasqを他のポートに移動、その後AGHをポート53で稼働させる。

arm-x41用のqpkgをダウンロード

このページを参考にダウンロードすべきqpkgのバージョンを確認する。QNAP 231PであればQNAP arm x41に該当する。バージョンを確認できたらQNAP CLUBからqpkgをダウンロードする。

インストール

Appセンターからqpkgを手動でインストール

ナビに従って初期設定

アプリを起動して言われるがままに設定。フォーラムではAGHの設定ページはポート9638とか書いてあるけど、この初期設定で自由に変更できる。DNSサーバとしてのポートは使用中と怒られない好きな数字を設定する。どうせ後で変更するから何でもいい。

AGHを停止

はい、私はここを読み落として苦戦してました。どうにも稼働中のアプリは、インストール先/share/CACHEDEV1_DATA/.qpkg/)とは別の場所(/opt/)にコピーもしくはリンクを貼られて変更できないようで、私は必死に/opt/のコピーを弄っていたため設定を変更できなかった。今から考えると書き込み時にそれっぽいログが表示されていた気がする。ログを読まずに痛い目にあったり解決が遅れるのは何度も経験してるのに、英文だと全然読むする習慣ができない、読んでも理解できない。

QNAPにssh接続

一時的にadminを有効化して、adminでssh接続

各種ファイルを変更

まずはAGHのポートの待ち受けポートを53に変更する。vimでファイルを開いて、
vi /share/CACHEDEV1_DATA/.qpkg/AdGuardHome/AdGuardHome.yaml
以下を設定する。
port: 53
protection_enabled: true
続いてプリインストールされているdnsmasqの設定を変える。
vi /etc/dnsmasq.conf
port = 1053
最後に次の設定を行う。
vi /etc/resolv.conf
nameserver = 127.0.1.1:1053

dnsmasqを再起動

killall -9 dnsmasq
dnsmasq
ユーザ名かユーザグループが正しくないと怒られたが私の場合は動いている。sshでの操作は終わりなのでログアウト、adminを無効化する。

AGHを再起動

Appセンターから起動すると無事、ポート53で稼働するようになっている。ルータもしくは各マシンのDNSサーバとしてQNAPのIPアドレスを設定すれば完了。

Macの画面共有にiPadからアクセスした

要約

実行環境

接続 マシン OS
MacBook Pro 13inch(mid 2014) Catalina
iPad Pro 11(2018) iOS 13.2.3

やったこと

  1. MacSSHVNCのポートをデフォルトから変更
  2. iPadからMacへ鍵認証方式でSSH接続を設定
  3. iPadVNCクライアントを導入
  4. Macで画面共有とリモートログインをオンにして接続

失敗したこと

  1. scpでのファイル転送
  2. RSA暗号化鍵が使えないVNCクライアントの購入

書かないこと

  • 外出先からのVPN接続
  • SSH鍵認証の設定方法

本題

何を何故やったのか

外出先からMacを触りたくなった時のために画面共有を設定した. Macの画面共有が使うVNCプロトコルは暗号化がされないため, そのまま使用するとどんな作業をしているか丸見えで恐ろしい. いくら自宅LAN内でファイアウォールに守られていようが, WiFiを使っていると結構不安になる. そもそもログインする時のユーザ名とパスワードがどんな状態で送信されてるのか.

対策としてVNC over SSHという方式で通信の安全性を高めた. SSHで暗号化と改竄防止を担保したトンネルを確立, その安全なトンネルにVNCプロトコルを通すと作業内容は秘匿できる. 本当はSSHのポートフォワード以外からは画面共有を受け付けないようにしたかったが, 調べても全く分からなかった. 仕方がないのでデフォルトポートの変更で我慢中.

前提としてiPadからMacへの鍵認証SSH接続は設定済み. 外出先からアクセスするためのVPNサーバも構築済みだ.

MacSSHVNCのポートをデフォルトから変更

MacSSH, VNCサーバを起動する前にやっておくべき. 鍵認証やパスワードで堅牢な守りを立てるのは当然だが, それでも不正アクセスを試みられたログはそこそこ残って精神衛生的に悪い. ポートも変えてしまった方が足跡も激減して安心できる. Macでのポートの変え方はこちらを参考にした.

スマホからSSHで自宅のMacへ接続する方法 (前編)

調べていると*.plistを変えないといけないと書いているページもあるが, 私の場合は/etc/servicesの変更だけで動作している. SSHは上のページの通りでいいが,VNC
rfb 5900/tcp vnc-server # VNC Server
rfb 5900/udp vnc-server # VNC Server
rfb xxxx/tcp vnc-server # VNC Server
rfb xxxx/udp vnc-server # VNC Server
と変える.xxxxには49152–65535の間から好きな数字を入れる. よくポートには1024以降の数字を指定すると書いてるページがあるが, wikipediaを見ると 1024–49151も何かしら使われてたりする. 被って不具合が出ても嫌だから49152–65535から選ぶと良さそう.

iPadからMacへ鍵認証方式でSSH接続を設定

よしなに.自分はBlinkを使ってssh-keygenした.

iPadVNCクライアントを導入

JUMP DESKTOPならVNC over SSHを使えて かつRSA暗号化鍵が使えた. 設定は特に難しいこともなく,最初にVNC接続の設定を作ってから 改めて編集でsshトンネルを追加する.

ただ秘密鍵の登録で困ったことがあった. Blinkで生成した秘密鍵をコピペしようとしても受け付けてくれなかったのだ. 苦肉の策でBlinkから秘密鍵iCloudフォルダにコピー, その後MacのFinderでJUMP DESKTOPのフォルダに秘密鍵を移動した. 秘密鍵クラウドを経由(同期が間に合ったかは知らないが)しているのが気持ち悪かった.

Macで画面共有とリモートログインをオンにして接続

何も苦労なく繋がった. 接続は簡単だが,Macで画面共有にかけたはずのパスワードが確認されないわ, 秘密鍵パスフレーズが2回目以降は確認されないわで 期待したセキュリティになっているかは怪しい. 後者についてはパスフレーズに嘘の文字列を登録しておくと 接続時に正しいパスフレーズを確認される.

失敗

scpでのファイル転送

秘密鍵Macに送れなかった.ssh接続はできるのに何故?

RSA暗号化鍵が使えないVNCクライアントの購入

このページを参考にRemoter Proを購入して秘密鍵をコピペしようとしたら
iPadでSSH&VNCを使ったリモート・デスクトップ環境構築

Remoter Proへの秘密鍵のコピペ

暗号化はDES-EDE3-CBCしか受け付けてくれない. 簡単に調べたところDESは暗号強度が低いからRSAに変えるべきらしく,今回のセキュアなVNCの目的からしたら論外.(暗号方式ついてはよく分かってないので違ったら教えて下さい)

以上