投稿

HTTP/1.1 Transfer-Encoding: chunked

Web Scraping 用のプログラムを組んでいて、HTTP レベルの Socket プログラムを自前で組むか、Client レベルの処理は言語環境のライブラリーに任せて HTML を解析・処理する程度のものを組むかという、二択問題によく悩む。これまでの経験上、後者の方が敷居が低く取りかかるのに容易であるというメリットがある。一方、一旦何らかの壁にぶち当たると、ブラックボックスのない前者の環境で試行錯誤を進めた方が、心理的にもまさしく“急がば回れ”の典型のような結果となり、トータルで楽になる。 そんなわけで、今回取り組んでいる物件についても、スタートアップは Java の URLConnection で一通り組み上げてしまって、それで問題なく全体的に動くものが出来上がっていたので、そのまま運用していたものだった。ゼロ状態からのスタートとなるプロジェクトでは、まずはどんな形であれ、全体としてちゃんと動くものへと到達できるかどうかが不明な状態なので、「まずは全体としてちゃんと動くもの」まで辿り着くことが何よりも重要である。そのためには、Client レベルのものは Java のコアライブラリーに任せて、Web Scraping にターゲットを絞った方が確実にプロジェクトを成功まで推進することができる。 それでそのまま動くようになったままで運用し続けていて、プログラムには手を入れていなかったのだが、今度、Lanterna を使って GUI 化したりして、使い勝手が向上したので、さらに前から欲しいと考えていた機能を追加したりした。最終的には GUI 化したらしたで CUI の時は気にならなかった処理速度が気になったので、ネットワークからのデータの読み込みをマルチスレッド化するところまで行き着いた。 このネットワーク読み込み処理のマルチスレッド化で、ある問題が表面化したのだった。 どうやら Java の URLConnection は、スレッドセーフではないようなのだ。とはいっても、このプロジェクトに関して言えば、Cookie がスレッド間で共有されていることが問題の核心であり、それを URLConnection 自体がスレッドセーフではないという話に含めていいのかどうかはわからない。ともかく、Cookie が上書きされてしまうのが原因で、サーバー側が付与する JS...

Exception の使い処

Perl では Exception のことは気にしないでプログラミングできたので、Java で当初特異に感じたのが Exception の扱いだ。仕様に強制される形で try-catch を行う必要があるという認識で今まで凌いできた。 そもそも CUI でプログラミングしている段階では、例外処理については別段の配慮はせずとも、コンソールに JVM が吐くエラーメッセージが表示されるので、自分でエラーに備える処理を明示的にコーディングする必要はなかった。CUI というのはそういった意味でユーザーとプログラマーが完全に同一レベルの視点で体験を共有しているので、ある意味貴重な環境なのかもしれない。 そういった CUI の貴重な環境の習慣をそのまま引き摺ってしまっていて、GUI で Android のアプリなどを作っていても、例外処理について、標準メソッド側が要求するから受動的に try-catch を記述する程度で、自分から能動的に Exception クラスを用いるという経験があまりなかった。 ところで最近 Web のフォーム送信操作を自動化する Java プログラムを作成していて、想定外のエラーが発生した時、そのエラーがどの段階で出たエラーなのかということがわかった方が、自分自身で利用するだけでなくもしアプリを公開するという前提では、その方が良いと考えた。そこで、各段階で、想定通りではない結果になっていた場合、Exception を発生させるようなコードにすることにした。 当初は字義的に、RuntimeException を使っていた。ところがこれでも「どの段階で発生したのか?」がわかりにくかったので、最終的には IOException を使うことに落ち着いた。 GUI ライブラリーとして Lanterna という ncurses ライクなライブラリーを使っており、コンソールではなく、GUI の表示にエラーメッセージも表示させたいと考えた。当初の RuntimeException だと、どこかでエラーが発生していることがコンソールには表示されるのだが、どの段階で起ったのかが把握しにくく、GUI 側でもどこでキャッチすればいいのかがわかりにくい。そこで IOException にして、GUI 側で catch させることにしたのである。 ネットワークにアク...

APFS は case-sensitive で

APFS を何も考えないでデフォルトのままフォーマットして macOS High Sierra をインストールして使っていたが、case-insensitive であることに気付いて、再度、SSD を完全に初期化し、case-sensitive フォーマット(非暗号化)にして、High Sierra をインストールし直した。 通常は、APFS を case-sensitive にする必要はなく、例えば、Android Studio(Android 用のアプリ開発環境)ではシステムのフォーマットが case-sensitive だと警告が出たりする。一方、OpenWrt のような Linux 系 OS のカーネルをクロスコンパイルする場合、Linux は case-sensitive なので、macOS 側でも case-sensitive である必要がある。そういったレアケースに対応したいのであれば、case-sensitive にしておく。そうでない場合はよっぽどでない限り、case-insensitive が無難だろう。 OS インストール後の各設定のカスタマイズや、基本ツールのインストールなどは、一度それぞれの方法を確立した後だったので、今回はそれらを通してメモにまとめておくことができた。FileZilla の設定 .xml はちゃんと iCloud ドライブに退避させておかなければならない、などの要点を整理するいい機会だった。 ちなみに、なぜ非暗号化でフォーマットしたかというと、一度暗号化でフォーマットしたのだが、OS のインストール後、暗号化されたドライブ用のユーザーアカウントが表示されるようになったので、気持悪かったので、非暗号化でフォーマットして仕切り直した次第である。OS のインストール完了後に、FileVault を有効化して暗号化したが、完全に暗号化が終わると、APFS のフォーマット自体が暗号化されたものに置き換った。ということは結局、最初から暗号化しておいた方が、時間的に遠回りしなくてスマートだったことになる(意味不明のユーザーアカウントはどうにか消す方法がある)。

QNAP の SSH のアクセス元制限の不完全な仕様

OpenVPN で実家の LAN につなぎ、VPN 経由で NAS(QNAP)にアクセスできるようになったので、ネットワークセキュリティを強化すべく、NAS の SSH にアクセス元制限を付けた。/root/.ssh/authorized_keys の公開鍵リストに、from="(ネットワークアドレス)" のオプション設定を追記したわけだ。 ところで、元々ブリッジモードの VPN 設定だったのを、ルーターモードに変更したため、アクセス元のクライアント機のネットワークアドレス(192.168.11.0/24)は NAS 側のネットワークアドレス(192.168.0.0/24)と異なるものとなった。そのため、from にカンマ(「,」)区切りでネットワークアドレスを列挙する必要がある。 from="192.168.0.0/24,192.168.11.0/24" ssh-rsa AAAAB3N... しかし、どうやら QNAP の SSHd では仕様が不完全なようで、from として列挙したものを与えると、正常に動作しない(すべてのネットワークからアクセスできなくなった。仕方なく、一時的に telnet を使って復旧作業をする羽目になった)。 つまり、authorized_keys の from 設定としては列挙で設定することはできず、一種類の設定しか与えることができないのである。 簡単に思い浮ぶのは、VPN をルーターモードで運用するのをやめて、ブリッジモードに戻すことである。そうすれば、クライアント機のネットワークアドレスは NAS 側のネットワークアドレスと同一なので、from のエントリーは一種類で済む。 それでもせっかくルーターモードにしたのにブリッジに戻すのも何となく嫌だったので、(専門家でもないのに独学でネットワークエンジニアの資格をパスしたという“昔取った杵柄”を活用できたのか)ポク・ポク・ポク・チーンと思い付いたのが、ネットワークアドレスに、192.168.0.0/23 という値を使う方法。 23 ビットというネットマスクを使う奇策 である。この設定は、192.168.0.0 〜 192.168.1.255 の範囲の IP を意味するネットワークアドレスである。 from="192.16...

OpenVPN を TAP(ブリッジ)→ TUN(ルーター)へ変更

イメージ
基本的に VPN と NFS を使った利用環境は構築できたので、最後に少しでも VPN を最適化しておきたくなり、OpenVPN のモードを TAP(ブリッジ)から TUN(ルーター)へ変更することにした。 .ovpn まずはサーバー(DD-WRT)側と、クライアント側(Mac、Linux)双方で、.ovpn 中の dev 設定を tap から tun に変えることが基本となる。 dev tun クライアント側はこれだけで他にやることはない。一方、サーバー側ではルーターモード制に伴ういくつかの副作用的な状況を解決するための設定が必要となる。 サーバー(DD-WRT)側 設定中で tun にする以外に、VPN 専用のサブネットの設定が必要になる。DD-WRT の設定の、TUN / TAP を選択する直下の Network と Netmask の部分がそれである。ここの例では 192.168.11.0/24 としてみた。 ブリッジモードではクライアントはそのままサーバー側のサブネット(192.168.0.0/24 とする)に参加する形となり、VPN サーバーが DHCP サーバーとしてクライアントに IP プールの中から一つ割り当てる形だった。ルーターモードでは、VPN 用のサブネット(192.168.11.0/24)丸ごとが IP プールとなりそれをクライアントに割り当てる。なので、VPN クライアントはサーバー側の本来のサブネットには直接参加しない形となるので、VPN 用のサブネットからサーバー側の本来のサブネットへとアクセスするためのルーティング設定が必要となる。そのために、DD-WRT の Additional Config においてクライアントにプッシュする形でルーティング設定をセットする。 push "redirect-gateway def1 bypass-dhcp" ※ "route 192.168.11.0 255.255.255.0" を別途 push する必要はない。redirect-gateway でリダイレクトされる Gateway 設定に同じ route 情報は含まれている。 さらに、これで VPN サブネット側のクライアントがサーバー側の本来のサブネットにアクセスでき...

ローカルディスク ⇄ NFS ディスク間の rsync

イメージ
👉 前回 で OpenVPN の接続と NFS のマウントまで自動化できたので、あとは特定のフォルダーをローカルディスク ⇄ NFS ディスク間における rsync をボタン一発でできるようにするだけだ。 最新の rsync macOS 標準の rsync はバージョンが旧いので、Homebrew で最新バージョンにするのが常套のようだ。 brew install rsync だけで ok。brew tap してからという情報は既に古いものなので注意。 OS 標準の元から存在する rsync は /usr/bin/rsync に、brew でインストールされた新しい rsync は /usr/local/bin/rsync にある。 ローカルディスク ⇄ NFS ディスク間における rsync 下の例では、~/eclipse-workspace フォルダを丸ごと、NFS にマウントした NAS の共有フォルダにバックアップしている。また、NAS 側で NFS のオプションを ALL_SQUASH で自分のアカウント名に割り当てているため、ファイルの所有者などの情報は rsync 側のオプションで制御していない。rsync 自体の詳しいオプションについては別途詳細を調べて欲しい。また下の例では省略したが、実際には --exclude オプションなども指定して、キャッシュデータは一々バックアップしないように等している。 /usr/local/bin/rsync -ruv --delete ~/eclipse-workspace /Volumes/NFS Automator によるアプリ化 上記 rsync のコマンド内容のシェルスクリプトを Automator を使ってアプリ化する。 Automator の使い方自体については 簡潔な解説 があり参考になる。

VPN 接続(Tunnelblick)と NFS マウントの自動化

👉 先の記事 で sudo がパスワード入力なしで実行できるようになったので、それを前提として VPN 接続に連動して NFS のマウント/アンマウントを自動化する方法を確立した。 (Tunnelblick のベースである)OpenVPN 自体の仕様で .ovpn ファイルの設定で up / down 用のスクリプトを指定できるようになっている。この仕様に加えて、Tunnelblick では、connected.sh と pre-disconnect.sh という名前のスクリプトをそれぞれ up / down 用スクリプトとして割り当てることによって「接続後」「切断前」に実行するスクリプトとし扱ってくれるようだ。 なので、connected.sh と pre-disconnect.sh を用意し、それぞれにおいて、NFS をマウント/切断する処理を記述しておいた。 connected.sh #!/bin/sh sudo mkdir /Volumes/NFS sudo mount_nfs -P 192.168.0.1:/share /Volumes/NFS pre-disconnect.sh #!/bin/sh sudo umount /Volumes/NFS ※ umount 時に /Volumes/NFS は自動的に rmdir されるので、rmdir は明示的に行う必要はない。 以上の 2 種のスクリプトを用意した上で、.ovpn に .ovpn up ~/OpenVPN/connected.sh down ~/OpenVPN/pre-disconnect.sh といったような形式の設定を追加する。 最後に、Tunnelblick で .ovpn をインポートする。これだけである。 ※ .ovpn や .sh を修正した場合、都度 Tunnelblick にインポートし直す必要がある点に注意する。Tunnelblick はインポート時に自前で .ovpn や .sh をコピーしてそれを直接使うので、インポート元の .ovpn や .sh を修正しただけでは Tunnelblick の挙動に反映されない。 Mac から NFS を使ってみた結果…… iCloud ドライブや Google ドライブのような感覚で使いたいと思...

sudo のパスワード入力を省略できるようにする

OpenVPN で、接続確立後に自動で NFS をマウントし、接続切断時(直前)に NFS をアンマウントするシェルスクリプトを使おうと思った。ところが、mount / umount には root 権限が必要なので、通常では sudo するのにパスワード入力が求められてしまう。 色々調べた結果、/etc/sudoers という設定ファイルによって制御が可能であり、さらに sudoers を編集するのには危険が伴うので、visudo を通じて編集するのが常識的だということがわかった。 macOS / Linux のいずれにおいても、基本的には %admin ALL=(ALL) ALL となっている部分を %admin ALL=(ALL) NOPASSWD:ALL とすればよい。これは管理者グループに所属するユーザーを対象に、sudo コマンドの実行時にパスワードを不要とする設定である。(また、このようにグループの設定からアプローチするやり方ではなく、個々のユーザー単位で同様の設定をするアプローチでも可能である) 注 1:エントリーの順番 Mac では root と %admin しか元々エントリーがなかったので、気にする必要はなかったが、Linux(Ubuntu GNOME 16.04LTS)では root / %admin / %sudo となっていて、グループのエントリーが %admin と %sudo の 2 種類あった。この状態で %admin のエントリーを NOPASSWD にしても %sudo のエントリーで上書きされてしまうので、エントリーの順序を入れ替えて、%sudo の後に %admin のエントリーが来るように編集しなければならない。 注 2:Ubuntu GNOME 16.04LTS での管理者グループ ID Ubuntu GNOME 16.04LTS では管理者グループの ID が admin ではなく、 adm であった。にもかかわらず、sudoer のエントリーは %admin となっていたため、%adm と修正する必要があった。 注 3:visudo visudo はその名の通り、vi がベースになっているので、操作がよくわからない場合は vi の情報を調べてみるとよい。

SSH の公開鍵認証+IP制限

過去に NAS(QNAP)の SSH の設定を色々いじっている時にも SSH について触れたこと があったが、その時は rsync の環境を構築しようとしていた記事の中で SSH も含めていたため、なぜ SSH 単独の話題ではなかったのかという事情についてまでは忘れていた。その時の本目的はあくまでも NAS を使ったデータのバックアップ環境の確立だったので、SSH 自体は使っても使わなくてもよかったからだ。 その当時においては、当初、VPN から NAS にアクセスして、NFS を使う方策を試みたが、VPN が PPTP を使ったセキュリティ的に脆弱なものであり、SSH によって rsync ができるようになったので、VPN も NFS も忘れていた。ところが、今現在、OpenVPN でセキュリティ的に十分な環境が整い、Mac から VPN 経由で NAS をクラウド的に利用する方法を模索し始めてみると、Samba よりも NFS の方がちゃんと動くし、Windows 抜きで Mac か Linux かで動かすのだから、よりによって(本来 Windows ちゃんと仲良く戯れるための)Samba など使わないに越したことはないのだ。 ともかく、VPN 前提で考えてみると QNAP の SSH のセキュリティも改めてちゃんと見直したくなり、アクセスを LAN からのアクセスに制限できないかと調べてみると、ちゃんと方法があったので、早速そうした。方法はサーバー(QNAP)側 .ssh/authorized_keys のエントリーに、from="192.168.0.0/24" のような形式のオプションを追加することである。次のような感じで: from="192.168.0.0/24" ssh-rsa AAAAB3...AgRiOf my Ubuntu from="192.168.0.0/24" ssh-rsa AAAAB3...v17zrd my Macbook こうすることで、SSH は LAN 内からの接続しか受け付けなくなった。 注 1: VPN 経由での接続 OpenVPN を使ってアクセスする際、ssh admin@192.168.0.1 のように、IP アドレスで NAS にアクセス...

VPN 環境の OpenVPN 化

イメージ
Mac をいじり出して、iCloud を軸にした標準アプリの設計思想が非常に気に入ってしまった。一般のユーザー視点なら無料 5GB で十分である。 ところが、Mac をメイン機とするならば、僕の場合、エンジニア(プログラマー)的側面も、一般ユーザーとしての立場に加えて必要になってくる。外にも持ち歩く MacBook に、開発中のデータが入っているのは非常にリスクが高いから、データの保管場所が MacBook の実機に依存するのは避けたい。だが、アプリを開発するためのプロジェクトファイルを iCloud ドライブにぶち込んでしまうと、どうやってもすぐに無料で利用できる 5GB の限界に達してしまう。かといって課金までして iCloud を増量したいとも思わない。 となると、せっかく実家で運用している QNAP 製の NAS があるので、それを使って iCloud のような運用ができないかと考えた。これまではデータを随時バックアップするという用途に NAS を使っていたのだが、クラウド的に、データ本体を常に NAS 側に置いておいて、それを端末側で参照するという形である。 ところが、写真などのマルテメディアデータはそのような専用のアプリが存在するのだが、ファイル一般となると iCloud ドライブのような使い方のできるオープンなアプリが存在しない。iCloud ドライブは macOS 専用だし、Google Drive にしても専用アプリをインストールする必要があり、それも Windows と macOS のみで Linux では使えない。 また、昔からある SMB の場合、LAN での利用を前提としていて、クラウドよりも一昔前のスタイルのまま停滞している感がある。 WebDAV 選択肢が限られていたので、まず最初に QNAP NAS で使える WebDAV を使ってみることにした。これは、macOS でも Linux でも標準で使えて、ネットワークドライブとして iCloud ドライブや Google Drive のようにアクセスが可能である。 ところが、WebDAV にはバグがあり、それは WebDAV が依存している apache のバグに由来するようだが、フォルダに index.html のファイルが存在すると、そのフォルダが空っぽ状態でファイル...