<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[てくにかるむ]]></title><description><![CDATA[「エラーをなくすことは非常に有益で時には新しい真実や事実を作り上げるよりも勝る」
ー チャールズ・ダーウィン]]></description><link>http://multix.jp/</link><generator>Ghost 0.6</generator><lastBuildDate>Tue, 10 Mar 2026 18:24:14 GMT</lastBuildDate><atom:link href="http://multix.jp/tag/windows/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[WebBrowserコンポーネントいろいろ]]></title><description><![CDATA[<p>WebBrowserコンポーネントを .NET Framework であれこれ料理したときのいろいろ。</p>

<ul class="index"></ul>

<hr>

<h3 id="">レンダリングバージョンを変更する</h3>

<p>WebBrowserコンポーネント<sup id="fnref:1"><a href="http://multix.jp/webbrowser-component-etc/#fn:1" rel="footnote">1</a></sup>は、何もせずにそのまま使用すると必ず IE7相当のレンダリングエンジンと JavaScriptエンジンになってしまう。これは <code>&lt;meta http-equiv="x-ua-compatible" content="IE=Edge" /&gt;</code> などを食わせても変わらない。<sup id="fnref:2"><a href="http://multix.jp/webbrowser-component-etc/#fn:2" rel="footnote">2</a></sup> この問題を解決するには、レジストリを触るしか方法がない。</p>

<pre><code class="language-brush:vb ">Try  
    Dim RendererVersion As Integer = 11000
    Dim ExecName As String = Path.GetFileName(Environment.GetCommandLineArgs()(0))
    Dim Regkey As RegistryKey = _
        Registry.CurrentUser.CreateSubKey( _
            "SOFTWARE\Microsoft\Internet Explorer\</code></pre>]]></description><link>http://multix.jp/webbrowser-component-etc/</link><guid isPermaLink="false">54302723-107c-4a26-93fd-27768651728a</guid><category><![CDATA[てくにかるむ]]></category><category><![CDATA[Windows]]></category><category><![CDATA[VB.NET]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Mon, 17 Apr 2017 09:22:12 GMT</pubDate><content:encoded><![CDATA[<p>WebBrowserコンポーネントを .NET Framework であれこれ料理したときのいろいろ。</p>

<ul class="index"></ul>

<hr>

<h3 id="">レンダリングバージョンを変更する</h3>

<p>WebBrowserコンポーネント<sup id="fnref:1"><a href="http://multix.jp/webbrowser-component-etc/#fn:1" rel="footnote">1</a></sup>は、何もせずにそのまま使用すると必ず IE7相当のレンダリングエンジンと JavaScriptエンジンになってしまう。これは <code>&lt;meta http-equiv="x-ua-compatible" content="IE=Edge" /&gt;</code> などを食わせても変わらない。<sup id="fnref:2"><a href="http://multix.jp/webbrowser-component-etc/#fn:2" rel="footnote">2</a></sup> この問題を解決するには、レジストリを触るしか方法がない。</p>

<pre><code class="language-brush:vb ">Try  
    Dim RendererVersion As Integer = 11000
    Dim ExecName As String = Path.GetFileName(Environment.GetCommandLineArgs()(0))
    Dim Regkey As RegistryKey = _
        Registry.CurrentUser.CreateSubKey( _
            "SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION")
    Regkey.SetValue(ExecName, RendererVersion)
    Regkey.Close()
Catch ex As Exception  
    Console.WriteLine("CreateSubKey: " &amp; ex.StackTrace)
End Try

WebBrowser1 = New WebBrowser()  
WebBrowser1.ObjectForScripting = Me  
</code></pre>

<p>レジストリには、キー＝実行ファイル(EXE)名、値＝IEバージョンの1000倍のDWORD値 を登録する。存在しない時の既定値は 7000 と見做される。またこのとき Visual Studio のデバッグRunでは .EXE名が <code>〜.vhost.exe</code> になるのを考慮する必要がある。結局のところ、プログラム実行毎に毎回自分自身の .EXE名を新規／上書き登録してしまうのが手堅い。またレジストリの登録タイミングは初めて <code>New WebBrowser()</code> する以前でなければならない。</p>

<hr>

<h3 id="vbnet">マウスイベントを VB.NET側で使いたい</h3>

<p>WebBrowserコンポーネント内で発生したイベントはほぼすべて JavaScript内で完結して外部（Windows.Forms）に伝播しない。必要なら JavaScriptから ObjectForScripting機能経由で Formクラスを呼んで RaiseEvent しろということになっている。だがそういう毎回使うような定形処理はサブクラス化してまとめといたほうが良いに決まっている。</p>

<pre><code class="language-brush:vb title: WebBrowserEx.vb">Imports System.Windows.Forms  
Public Class WebBrowserEx  
    Inherits WebBrowser

    Private WithEvents Body As HtmlElement

    Public Shadows Event MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs)
    Public Shadows Event MouseLeave(ByVal sender As Object, ByVal e As MouseEventArgs)

    Protected Overrides Sub OnDocumentCompleted(ByVal e As WebBrowserDocumentCompletedEventArgs)
        Me.Body = Me.Document.Body
        MyBase.OnDocumentCompleted(e)
    End Sub

    Private Sub Body_MouseDown(ByVal sender As Object, _
                               ByVal e As HtmlElementEventArgs) _
                           Handles Body.MouseDown
        RaiseEvent MouseDown(sender, New MouseEventArgs(e.MouseButtonsPressed, 1, _
                                                        e.ClientMousePosition.X, _
                                                        e.ClientMousePosition.Y, 0))
    End Sub

    Private Sub Body_MouseLeave(ByVal sender As Object, _
                                ByVal e As HtmlElementEventArgs) _
                            Handles Body.MouseLeave
        RaiseEvent MouseLeave(sender, New MouseEventArgs(MouseButtons.None, 0, _
                                                         e.ClientMousePosition.X, _
                                                         e.ClientMousePosition.Y, 0))
End Class

' WebBrowser1 = New WebBrowserEx()  
' WebBrowser1.Navigate("about:blank")  
</code></pre>

<p>ここでは一部しか書いてないが、Up/Enter/Move/Over も同様に実装<sup id="fnref:3"><a href="http://multix.jp/webbrowser-component-etc/#fn:3" rel="footnote">3</a></sup>できる。</p>

<p>ただこの実装においては、WebBrowserコンポーネント内で Refresh() する、あるいはF5(Ctrl+R)キーを押してのページリロードに対応できない。というのも .NET版の WebBrowserコンポーネントはリロード時に一切イベントを発火しないからだ。ページ内のJavaScriptでは通常通り onload等が発火するので ObjectForScriptingで補完する（Me.Document.Body を読み直す）必要がある。</p>

<hr>

<h3 id="jscripttypeinfonet">JScriptTypeInfo と .NET オブジェクトの相互変換</h3>

<p>ObjectForScriptingと InvokeScriptを使えば .NETと JavaScriptのユーザ関数を相互に呼び出し合うことが出来る。この時確実に引き渡せる引数は、数値・文字列・真偽値・null の4種類しかない。配列や連想配列（ハッシュ）はシリアライズする必要がある。だが実際には次のコードを試せば解るが、JavaScriptで生成された複雑なデータ構造は .NETのなかを素通りさせることができる。</p>

<pre><code class="language-brush:javascript title:JavaScript">var r = window.external.foo({a:[123, 456], h:{s:789}, s:"Test"});  
function bar (data) {  
    return {b:data.a[0] + data.h.s};
}
console.log(r.b); // 912  
</code></pre>

<pre><code class="language-brush:vb title:VB.NET">Public Function foo (ByVal d As Object) As Object  
    Console.log(d.s)    ' Test
    Console.log(d.a(1)) ' 456
    Console.log(d.h.s)  ' 789
    Return WebBrowser1.Document.InvokeScript("bar", d)
End Function  
</code></pre>

<p>WebBrowserコンポーネントにはこのJavaScriptネイティブのデータ構造＝JScriptTypeInfo型オブジェクトを解釈する機能（オブジェクト定義）がない。単純な文字列や数値は ToString()や ToInt32()メソッドで変換できるし、ハッシュの場合は Item() メソッドこそ使えるものの Count() も Containes() もないので「静的な構造体」以外は直接扱えないのだ。</p>

<pre><code class="language-brush:vb">Console.log(d.s)               ' sメンバーがあれば読み出せるがなければ例外発生  
Console.log(d.Item("s"))       ' これもメンバーがなければ例外発生  
Console.log(d.Conatines("s"))  ' これもメンバーがなければ例外発生  
</code></pre>

<p>.NETのほうでは3.5以降で JSON文字列をシリアライズ／デシリアライズ出来るため、引数も返値も JSON文字列化する取り決めにすれば良い話なのだが、ネイティブにデータ構造を受け渡しできたほうが（JavaScript側の視点では）利便性が良い。もっぱら融通が効かないのは静的型言語である .NETの側なので、こちらをもう少し頑張ってみよう。</p>

<p>取り敢えず、JScriptTypeInfoを読んで比較的扱いやすい Dictionary型にまるごと変換するなら次のようなコードが書ける。いったん IExpando型にキャストすればメンバーのプロパティリストを得られるので、CallByName()で実体を得ることができる。これを再帰的に適用すればいちおう全体にアクセスすることが出来る。ただし配列については手を抜いてハッシュ化してしまっているが。</p>

<pre><code class="language-brush:vb title:JScriptTypeInfo型を Dictionary型に変換する（暫定）">' Imports System.Reflection  
' Imports System.Runtime.InteropServices.Expando

Private Function JscInfoToDict(ByVal jscinfo As Object) As Dictionary(Of String, Object)  
    Dim dict = New Dictionary(Of String, Object)
    Dim keys() As String = DirectCast(jscinfo, IExpando).GetProperties( _
        BindingFlags.Default).Select(Function(p) p.Name).ToArray()
    For Each Key In keys
        Dim Var As Object = CallByName(jscinfo, Key, CallType.Get)
        ' Console.WriteLine("Key:" &amp; Key &amp; ", Var:" &amp; Var.ToString &amp; ", Type:" &amp; Var.GetType.ToString)
        If Var.GetType.ToString = "System.__ComObject" Then  ' これだけでは配列とハッシュの区別がつけられない
            dict.Add(Key, JscInfoToDict(Var))
        Else
            dict.Add(Key, Var)
        End If
    Next
    Return dict
End Function  
</code></pre>

<p>これとは逆に、Dictionary型を JScriptTypeInfo型に変換するのは、JScriptTypeInfo型の定義がないので不可能だ。ならば「JScriptTypeInfo型を知っている」言語に変換を委託してしまえば良い。そもそも相互変換をしたい場面において WebBrowserコンポーネントを使っていないということは滅多にないだろうから、InvokeScriptで JSONクラスメソッドを呼び出してしまえば良い。<sup id="fnref:4"><a href="http://multix.jp/webbrowser-component-etc/#fn:4" rel="footnote">4</a></sup></p>

<pre><code class="language-brush:vb title:相互変換">' Imports System.Web.Script.Serialization

' Friend WithEvents Converter As WebBrowser  
' Converter = New WebBrowser()  
' Converter.Navigate(New Uri("about:blank"))

Private Sub Converter_DocumentCompleted(ByVal sender As Object, _  
                                        ByVal e As EventArgs) _
                                    Handles Converter.DocumentCompleted
    sender.Document.InvokeScript("eval", New Object() {"function __j2s(j){return JSON.stringify(j)};"})
    sender.Document.InvokeScript("eval", New Object() {"function __s2j(j){return JSON.parse(j)};"})
End Sub

Public Function jobject_decoder(ByVal jscinfo As Object) As Dictionary(Of String, Object)  
    Dim jss As JavaScriptSerializer = New JavaScriptSerializer()
    Dim jstring As String = Converter.Document.InvokeScript("__j2s", New Object() {jscinfo})
    Return jss.Deserialize(Of Dictionary(Of String, Object))(If(jstring, "{}"))
End Function

Public Function jobject_encoder(ByVal dict As Dictionary(Of String, Object)) As Object  
    Dim jss As JavaScriptSerializer = New JavaScriptSerializer()
    Dim jstring As String = jss.Serialize(dict)
    Return Converter.Document.InvokeScript("__s2j", New Object() {jstring})
End Function  
</code></pre>

<p>.NET版の InvokeScript() は COM版のそれとは異なり、ルート要素のユーザ関数しか呼び出せず、クラスメソッドを直接叩くことが出来ない。本来なら <code>JSON.stringify</code> <code>JSON.parse</code> を直接使いたいが使えないので、DocumentCompleted を待ってから変換用ユーザ関数を eval で登録する方法を取った。なお JSONクラスは IE7ではそもそも存在しないので、前述した IEバージョン変更のレジストリ登録が事前に必要になる。<sup id="fnref:5"><a href="http://multix.jp/webbrowser-component-etc/#fn:5" rel="footnote">5</a></sup></p>

<hr>

<h3 id="cscriptexejson">余談：cscript.exeでの JSON変換</h3>

<p>Windows標準でかつ標準入出力が使える汎用インタプリタとして、cscript.exe は貴重な存在だ。うまく使えば grep/sed/awk あたりの代用にもなる。だが標準では JSON変換機能を持っていないなど基本的な言語仕様が少々古い。しかし COMオブジェクトとして HTMLファイルにアクセスすると、その中の最新 JavaScriptエンジンから欲しい機能をアドオン的に持ってくることが出来る。</p>

<pre><code class="language-brush:javascript title:jconvert.js">// Ex) cscript.exe //Nologo //U jconvert.js &lt;STDIN &gt;STDOUT
var htmlfile = WScript.CreateObject('htmlfile'), JSON;  
htmlfile.write('&lt;meta http-equiv="x-ua-compatible" content="IE=9" /&gt;');  
htmlfile.close(JSON = htmlfile.parentWindow.JSON);

while (!WScript.StdIn.AtEndOfStream) {  
    var json = JSON.parse(WScript.StdIn.ReadLine()),
    // user code
    WScript.StdOut.WriteLine(JSON.stringify(json));
}
</code></pre>

<p>このテクニックは色々と応用が効く。同じようにして（役に立つかどうかはともかく）jQueryをインポートすることが出来るし、include／require文代わりに使うことも出来る。</p>

<p>なお cscript.exe の標準入出力は、素で起動すると ANSI すなわち日本語では CP932 になる。このため JSON文字列中のマルチバイト文字はエスケープされていなければ正しく扱えない。そこで //U オプション付きで起動すると UTF-16LE でマルチバイト文字を扱えるようになるが、改行コードも2バイト文字（ワード幅）にしなければならない罠がある。更に余談となるが、cscript.exe のスクリプトコードは、ANSI(CP932)または UTF-16LE（BOM付き）で記述されていなければならない。一般的な UTF-8N では文字化けが発生する。<sup id="fnref:6"><a href="http://multix.jp/webbrowser-component-etc/#fn:6" rel="footnote">6</a></sup></p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>ここでのサンプルコードは VB.NET だが、C#NETでも JScript.NET でも基本は変わらない。.NETではどの言語でコードを記述しようと、結果的に生成されるのは同一の共通言語オブジェクトだ。なお .NET Frameworkのバージョンは 4.0以降を前提とする。いまさら Vista 以前を動作対象にすることもないし、Win7でも 4.0 Runtime を入れれば済むことだ。 <a href="http://multix.jp/webbrowser-component-etc/#fnref:1" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:2"><p>x-ua-compatible が実装されたのはIE8以降なのだから当然なのだが。 <a href="http://multix.jp/webbrowser-component-etc/#fnref:2" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:3"><p>UpはDownと、Enter/Move/OverはLeaveとおなじ MouseEventArgs(...) 宣言を書く。 <a href="http://multix.jp/webbrowser-component-etc/#fnref:3" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:4"><p>ここで使っている Converterはコントロール表示には使わないので Windows.Forms.Controls に Add する必要はない。 <a href="http://multix.jp/webbrowser-component-etc/#fnref:4" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:5"><p>IEバージョンを変えないならば、Navigate() で読み込むページのなかのほうに、JSON変換関数を JavaScriptで自作して記述することになる。もっともそこまでの手間を掛けるなら、.NET側で頑張るよりも JavaScriptの方で普通に JSON変換して受け渡したほうがコード記述量は増えるものの、単純だろう。 <a href="http://multix.jp/webbrowser-component-etc/#fnref:5" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:6"><p>これに気付いておらず「wscript／cscript は日本語が扱えない」とする発言や書籍がままあるので注意を要する。Windows本来のネイティブ文字コードは結構な昔から「ANSIおよびUnicode(16)」と決まっている。「メモ帳」が UTF-8Nファイルをそのまま保存せず、おせっかいにもBOMを挿入したりする所以もまたこれである。 <a href="http://multix.jp/webbrowser-component-etc/#fnref:6" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[ActivePerlのオフライン構築]]></title><description><![CDATA[<p>ActivePerl 本体は実行インストーラで何時でもインストールできるが、ppm や cpan で組み込む追加モジュールはオンライン構築が前提になっている。だが世間から断絶された秘匿ネットワークや、そもそもインターネットに接続するという概念のない PCやサーバに納品したり、機材リプレースで開発環境を再構築しなければならない場面では、USBメモリや DVD-Rからのオフラインインストールが出来なければ話にならない。そもそもバージョンが数世代古くなるとダウンロード元の ppmレポジトリが有料プラン専用に切り替えられて、いつもで気軽に再ダウンロードすることができなくなってしまう。それでは10年以上メンテ契約が続くような案件では、開発当時の状況保存は結構重要な問題になるので予防線を張っておくことは必要なのだ。</p>

<hr>

<h3 id="ppmx">.ppmxファイルの保存</h3>

<p>ppm install を実行すると、レポジトリから .ppmxファイルが tempディレクトリにダウンロードされてインストールされる。だがこのファイルはコマンド終了時に全削除されるため、普段目にすることがない。.ppmxファイル自体は ppm install の引数に渡すことでオフラインインストールできるため、消される前にファイルのコピーを別の場所に取っておけば、あとでおなじ環境を再構築することが容易くなる。</p>

<pre><code class="language-brush:perl gutter:true title:Client.patch">--- C:/Perl64/lib/ActivePerl/PPM/Client.pm      Tue Feb 16 06:02:11 2016
+++ D:/Perl64/lib/</code></pre>]]></description><link>http://multix.jp/activeperl-offline-installation/</link><guid isPermaLink="false">db8a724c-e2cd-49a8-9217-2a6cf62cc337</guid><category><![CDATA[てくにかるむ]]></category><category><![CDATA[Windows]]></category><category><![CDATA[ActivePerl]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Fri, 10 Mar 2017 04:08:26 GMT</pubDate><content:encoded><![CDATA[<p>ActivePerl 本体は実行インストーラで何時でもインストールできるが、ppm や cpan で組み込む追加モジュールはオンライン構築が前提になっている。だが世間から断絶された秘匿ネットワークや、そもそもインターネットに接続するという概念のない PCやサーバに納品したり、機材リプレースで開発環境を再構築しなければならない場面では、USBメモリや DVD-Rからのオフラインインストールが出来なければ話にならない。そもそもバージョンが数世代古くなるとダウンロード元の ppmレポジトリが有料プラン専用に切り替えられて、いつもで気軽に再ダウンロードすることができなくなってしまう。それでは10年以上メンテ契約が続くような案件では、開発当時の状況保存は結構重要な問題になるので予防線を張っておくことは必要なのだ。</p>

<hr>

<h3 id="ppmx">.ppmxファイルの保存</h3>

<p>ppm install を実行すると、レポジトリから .ppmxファイルが tempディレクトリにダウンロードされてインストールされる。だがこのファイルはコマンド終了時に全削除されるため、普段目にすることがない。.ppmxファイル自体は ppm install の引数に渡すことでオフラインインストールできるため、消される前にファイルのコピーを別の場所に取っておけば、あとでおなじ環境を再構築することが容易くなる。</p>

<pre><code class="language-brush:perl gutter:true title:Client.patch">--- C:/Perl64/lib/ActivePerl/PPM/Client.pm      Tue Feb 16 06:02:11 2016
+++ D:/Perl64/lib/ActivePerl/PPM/Client.pm      Fri Mar 10 12:12:03 2017
@@ -1328,6 +1328,9 @@
                    if ($save_len != $len) {
                        die "Aborted download ($len bytes expected, got $save_len).\n";
                    }
+use File::Copy;
+my $File = File::Basename::basename($save, '.tgz');
+File::Copy::copy($save, $File.'.ppmx');
                }
                # XXX An MD5 checksum for the tarball would be a good thing
            }
</code></pre>

<p>5.18以降に通用するこのパッチを当てると、カレントディレクトリにダウンロードされた .ppmxファイルが残されるようになる。依存関係で複数のモジュールが取得された場合はその全てが残される。このコードを見れば解るが、 .ppmxファイルの実態は単なる .tgzアーカイブだ。わざわざ拡張子を代えているのは、そうしないと ppmコマンドがオフラインインストール用ファイル名として認識しないからである。</p>

<p>なお ppmコマンドは自前のデータベースとキャッシュでダウンロード履歴を管理しており、毎回必ずしもファイルダウンロードが発生するわけではない。情報は以下のフォルダに保存されているので、作業前にこれを空にしてやればよい。</p>

<pre><code>%LOCALAPPDATA%\ActiveState\ActivePerl
</code></pre>

<p>保存しておいた .ppmxファイルは、ppm install でインストールすることが出来るが、依存関係にあるサブモジュールを先にインストールしておかないと、オンラインで取りに行こうとしてしまう。そこで複数の .ppmxファイルから .ppdファイルを取り出し、そこに書かれた依存関係リストを読み出し、優先順位を付けて順次処理することになる。</p>

<pre><code class="language-brush:perl gutter:true title:LocalUpdate.pl">#!C:/Perl/bin/perl.exe
use utf8;  
use strict;  
use Archive::Tar;  
use File::Basename;  
use File::Find;  
use Cwd;

my $cwd = $ARGV[0] // getcwd;  
print "Search directory: ", $cwd, "\n";  
my(@ppmx_list, %ppmx_hash, %ppmx_path);  
find(sub {  
    if (m/\.ppmx$/io) {
        $ppmx_path{$_} = $File::Find::name;
        push @ppmx_list, $_;
    }
}, $cwd);
foreach my $ppmx (@ppmx_list) {  
    $ppmx_hash{$ppmx} = 9999;
}
foreach my $ppmx (@ppmx_list) {  
    my $tar = Archive::Tar-&gt;new();
    $tar-&gt;read($ppmx_path{$ppmx}, 1);
    foreach my $file ($tar-&gt;get_files()) {
        if ($file-&gt;{name} =~ /\.ppd$/io) {
            foreach my $line (split /\n/, $file-&gt;{data}) {
                if (my $require = ($line =~ m/REQUIRE NAME="([^\"]+)"/o)[0]) {
                    $require =~ s/::/-/go;
                    foreach my $hash (keys %ppmx_hash) {
                        $ppmx_hash{$hash}-- if 0 == index lc($hash), lc($require);
                    }
                }
            }
        }
    }
}
undef @ppmx_list;  
while (my($key, $var) = each %ppmx_hash) {  
    push @ppmx_list, sprintf "%04u %s", $var, $key;
}
foreach my $ppmx (sort {lc $a cmp lc $b} @ppmx_list) {  
    $ppmx =~ s/^\d+ //o;
    my $command = "ppm install ".$ppmx_path{$ppmx}." --nodeps";
    print $cwd, "&gt; ", $command, "\n";
    system $command and die "$!";
    print "\n";
}

1;  
__END__  
</code></pre>

<p>このコードは .ppmxファイルを含む親ディレクトリを引数に取る。引数は複数でもよく、省略されればカレントディレクトリを起点にする。-nオプションは ppmコマンドに --nodeps を付加する。-n オプションなしで起動すると依存解決に失敗した場合（依存する .ppmxが見つからない場合）レポジトリからのダウンロードが生じる。</p>

<hr>

<h3 id="ppmx">.ppmxファイルの作成</h3>

<p>.ppmxファイルの実態は .tgzで固められた .ppdファイルと blibディレクトリだ。cpanでソースからビルドしたものを .ppmxファイルにして保存しておくと、再ビルドする手間を減らせる。cpanの作業ワークは <code>C:\Perl64\cpan\build</code> または <code>C:\Perl\cpan\build</code> にある。</p>

<p>例えば既に cpan install に成功した <code>Foo::Bar</code> モジュールに対応する <code>Foo-Bar-0.1010-4UofsT</code> というディレクトリがあったなら、まずそこに cd して <code>dmake ppd</code> を実行<sup id="fnref:1"><a href="http://multix.jp/activeperl-offline-installation/#fn:1" rel="footnote">1</a></sup>する。すると <code>Foo-Bar.ppd</code> というファイルが作成されるだろう。</p>

<pre><code class="language-brush:bash">&gt; cd C:\Perl64\cpan\build\Foo-Bar-0.1010-4UofsT
&gt; dmake ppd
&gt; type Foo-Bar.ppd
&lt;SOFTPKG NAME="Foo-Bar" VERSION="0.1010"&gt;  
    &lt;ABSTRACT&gt;foobar foobar&lt;/ABSTRACT&gt;
    &lt;AUTHOR&gt;Charlie Root &amp;lt;foobar@example.com&amp;gt;&lt;/AUTHOR&gt;
    &lt;IMPLEMENTATION&gt;
        &lt;PERLCORE VERSION="5,006,0,0" /&gt;
        &lt;REQUIRE NAME="Carp::" /&gt;
        &lt;REQUIRE NAME="Cwd::" VERSION="3.16" /&gt;
        &lt;REQUIRE NAME="Exporter::" /&gt;
        &lt;REQUIRE NAME="strict::" /&gt;
        &lt;REQUIRE NAME="vars::" /&gt;
        &lt;ARCHITECTURE NAME="MSWin32-x64-multi-thread-5.24" /&gt;
        &lt;CODEBASE HREF="" /&gt;
    &lt;/IMPLEMENTATION&gt;
&lt;/SOFTPKG&gt;  
</code></pre>

<p>REQUIRE 行は依存をあらわす。ARCHITECTURE 行はインストール先のプラットフォームを限定するものだが、ピュアPerlで書かれた環境非依存モジュールの場合は <code>noarch</code> に書き換えることでインストール先を選ばないようにすることもできる。</p>

<pre><code class="language-brush:xml ">&lt;ARCHITECTURE NAME="noarch" /&gt;  
</code></pre>

<p>.ppmx(.tgz)ファイルを作るための tar+gzアーカイバは、C:\Perl64\bin に <code>ptar.bat</code>としてインストールされている。すでにパスは通っているので次のようにすれば .ppmxファイルができあがる。</p>

<pre><code class="language-brush:bash ">&gt; ptar -v -c -C -z -f foo-bar.ppmx blib foo-bar.ppd
</code></pre>

<p>この際注意が必要なのは、.ppmxと .ppdのファイル名は一致させておかなければならない点だ。.ppmxファイル名にバージョン番号を付加する一般的なルールに従う場合は、.ppdファイル名にもバージョン番号を付加しておく。もちろんそのバージョン番号は SOFTPKG行の VERSIONと一致しているべきだ。</p>

<pre><code class="language-brush:bash ">&gt; ren foo-bar.ppd foo-bar-0.1010.ppd
&gt; ptar -v -c -C -z -f foo-bar-0.1010.ppmx blib foo-bar-0.1010.ppd
</code></pre>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>dmake.exe は cpan初期化時に C:\Perl64\sit\bin にインストールされているはずだ。 <a href="http://multix.jp/activeperl-offline-installation/#fnref:1" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[windowsで名前付きパイプを作る]]></title><description><![CDATA[<p>ActivePerl で Win32::Pipe が正常動作しなかった（AutoLoaderが実行時に異常終了する）ので Win32::API を直接叩いてみた。</p>

<hr>

<p>クライアント側は普通に <em>open/close</em> で読み書きできるので、サーバ側で namedpipe を作る部分を書いてみる。</p>

<pre><code class="language-brush:perl gutter:true title:win32pipeserver.pl">use strict;  
use warnings;  
use Win32::API;  
use Fcntl;  
use constant {  
    PIPE_ACCESS_INBOUND     =&gt; 0x00000001,  # O_RDONLY相当
    PIPE_ACCESS_OUTBOUND    =&gt; 0x00000002,  # O_WRONLY相当
    PIPE_ACCESS_DUPLEX      =&gt; 0x00000003,  # O_RDWR相当

    PIPE_</code></pre>]]></description><link>http://multix.jp/win32-create-namedpipe/</link><guid isPermaLink="false">50f30e5a-4293-4576-84e3-6753b5ef8ec4</guid><category><![CDATA[てくにかるむ]]></category><category><![CDATA[Windows]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Mon, 20 Feb 2017 09:33:48 GMT</pubDate><content:encoded><![CDATA[<p>ActivePerl で Win32::Pipe が正常動作しなかった（AutoLoaderが実行時に異常終了する）ので Win32::API を直接叩いてみた。</p>

<hr>

<p>クライアント側は普通に <em>open/close</em> で読み書きできるので、サーバ側で namedpipe を作る部分を書いてみる。</p>

<pre><code class="language-brush:perl gutter:true title:win32pipeserver.pl">use strict;  
use warnings;  
use Win32::API;  
use Fcntl;  
use constant {  
    PIPE_ACCESS_INBOUND     =&gt; 0x00000001,  # O_RDONLY相当
    PIPE_ACCESS_OUTBOUND    =&gt; 0x00000002,  # O_WRONLY相当
    PIPE_ACCESS_DUPLEX      =&gt; 0x00000003,  # O_RDWR相当

    PIPE_WAIT               =&gt; 0x00000000,
    FILE_FLAG_OVERLAPPED    =&gt; 0x40000000,
};

*STDOUT-&gt;autoflush;

my $PIPE_NAME = "\\\\.\\pipe\\pipesv";

my $CreateNamedPipe = Win32::API-&gt;new( "kernel32", "CreateNamedPipe", "PNNNNNNP", "N" ) or die;  
my $PIPE = $CreateNamedPipe-&gt;Call(  
    $PIPE_NAME,                #  LPCTSTR lpName,                             // パイプ名
    PIPE_ACCESS_DUPLEX,     #  DWORD dwOpenMode,                           // パイプを開くモード
    PIPE_WAIT,              #  DWORD dwPipeMode,                           // パイプ固有のモード
    255,                    #  DWORD nMaxInstances,                        // インスタンスの最大数
    0,                      #  DWORD nOutBufferSize,                       // 出力バッファのサイズ
    0,                      #  DWORD nInBufferSize,                        // 入力バッファのサイズ
    10_000,                 #  DWORD nDefaultTimeOut,                      // タイムアウト(msec)の間隔
    0,                      #  LPSECURITY_ATTRIBUTES lpSecurityAttributes  // セキュリティ記述子
);
my $CloseHandle = Win32::API-&gt;new( "kernel32", "CloseHandle", "N", "N" ) or die;

my $ReadFile = Win32::API-&gt;new( "kernel32", "ReadFile", "NPNPP", "N" ) or die;  
sub ReadFile {  
    my $hFile = shift;
    my $lpBuffer = "\0" x 512;
    my $nNumberOfBytesToRead = 512;
    my $lpNumberOfBytesRead = "\0" x 8;
    my $result = $ReadFile-&gt;Call(
        $hFile,
        $lpBuffer,
        $nNumberOfBytesToRead,
        $lpNumberOfBytesRead,
        0);
    return undef unless $result;
    my $length = unpack "V", $lpNumberOfBytesRead;
    return undef unless $length;
    return substr $lpBuffer, 0, $length;
}

my $WriteFile = Win32::API-&gt;new( "kernel32", "WriteFile", "NPNPP", "N" ) or die;  
sub WriteFile {  
    my $hFile = shift;
    my $lpBuffer = shift;
    while (my $nNumberOfBytesToWrite = defined $lpBuffer &amp;&amp; length $lpBuffer) {
        my $lpNumberOfBytesWritten = "\0" x 8;
        my $result = $WriteFile-&gt;Call(
            $hFile,
            $lpBuffer,
            $nNumberOfBytesToWrite,
            $lpNumberOfBytesWritten,
            0);
        return undef unless $result;
        my $length = unpack "V", $lpNumberOfBytesWritten;
        $lpBuffer = substr $lpBuffer, $length;
    }
    return undef;
}

my $ConnectNamedPipe = Win32::API-&gt;new( "kernel32", "ConnectNamedPipe", "NP", "N" ) or die;  
my $DisconnectNamedPipe = Win32::API-&gt;new( "kernel32", "DisconnectNamedPipe", "N", "N" ) or die;

while ($ConnectNamedPipe-&gt;Call($PIPE, 0)) {  
    WriteFile($PIPE, "Welcome\n");
    print ReadFile($PIPE);         # client from "Hello"
    $DisconnectNamedPipe-&gt;Call($PIPE);
    #last;      # no loop
}
$CloseHandle-&gt;Call($PIPE);

1;  
__END__  
</code></pre>

<p>本来はセキュリティ記述子を指定すべきだが、本件は動作サンプルなので省略<sup id="fnref:1"><a href="http://multix.jp/win32-create-namedpipe/#fn:1" rel="footnote">1</a></sup>する。バッファサイズについてはダミーなので 0 と書いて良い。
こうして出来た PIPEハンドル（単なるint値）は、PerlIOの感知するところではないため<sup id="fnref:2"><a href="http://multix.jp/win32-create-namedpipe/#fn:2" rel="footnote">2</a></sup> 通常の sysread/syswrite では扱えない。従って CloseHandle/WriteFile/ReadFile についても Win32API で実装する。</p>

<p><code>ConnectNamedPipe()</code> はソケット通信の accept() に相当する関数で、クライアント側が同名パイプを開くまでI/Oブロックする。<code>DisconnectNamedPipe()</code>はこのセッションを閉じてクライアントを切断する。</p>

<p>いっぽう、クライアント側は普通のファイルアクセスとなんら変わるところはない。</p>

<pre><code class="language-brush:perl gutter:true title:win32pipeclient.pl">use strict;  
use warnings;

*STDOUT-&gt;autoflush;

my $PIPE_NAME = "\\\\.\\pipe\\pipesv";

open my $FH, "+&lt;", $PIPE_NAME or die "$!";  
$FH-&gt;binmode(":raw");
print scalar &lt;$FH&gt;;                # server from "Welcome"  
$FH-&gt;write("hello\n");
$FH-&gt;close;

1;  
__END__  
</code></pre>

<p>マルチクライアントサーバを実装する場合、基本的には ConnectNamedPipe のあとで CreateThread すればよいが、CreateNamedPipe に FILE_FLAG_OVERLAPPED の指定と、ConnectNamedPipe に lpOverlapped の指定が必要になる。<sup id="fnref:3"><a href="http://multix.jp/win32-create-namedpipe/#fn:3" rel="footnote">3</a></sup></p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>従って誰でもどこからでも無制限に読み書きできてしまう。その場合何が起こり得るかについては<a href="http://eternalwindows.jp/windevelop/service/service06.html">こちら</a>が詳しい。 <a href="http://multix.jp/win32-create-namedpipe/#fnref:1" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:2"><p>open($DUP, "&lt;&amp;=", $fd) といった構文では認識できない。 <a href="http://multix.jp/win32-create-namedpipe/#fnref:2" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:3"><p><a href="https://msdn.microsoft.com/ja-jp/library/cc429611.aspx">https://msdn.microsoft.com/ja-jp/library/cc429611.aspx</a> <a href="http://multix.jp/win32-create-namedpipe/#fnref:3" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[IIS用FastCGI簡易ラッパー]]></title><description><![CDATA[<p><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/">前項</a>でIIS用FastCGIについて述べた件の続き。</p>

<hr>

<p>モジュールマップ登録では個々の.fcgiファイルについて1エントリが必要とされるため、拡張子.fcgi全体をひとつのモジュールマップで済ませるための簡単なラッパーを書いてみた。ものすごく単純だが用法を誤らなければ相応に使える。</p>

<pre><code class="language-brush:perl gutter:true title:fcgi.pl">use strict;  
use warnings;  
use FCGI;  
use File::Spec;  
open STDERR, "&gt;&amp;", STDOUT;  
my $request = FCGI::Request or die;  
while ( $request-&gt;Accept &gt;= 0 ) {  
    my $stderr = '';
    eval {
        local $SIG{__WARN__} = sub { $stderr .= shift };
        local $SIG{__DIE__} = sub { die($stderr = shift)</code></pre>]]></description><link>http://multix.jp/iis-fastcgi-easy-wrapper/</link><guid isPermaLink="false">d447898a-80b8-401a-aab0-e76227fcb7f8</guid><category><![CDATA[Windows]]></category><category><![CDATA[Web]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Mon, 20 Feb 2017 09:25:55 GMT</pubDate><content:encoded><![CDATA[<p><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/">前項</a>でIIS用FastCGIについて述べた件の続き。</p>

<hr>

<p>モジュールマップ登録では個々の.fcgiファイルについて1エントリが必要とされるため、拡張子.fcgi全体をひとつのモジュールマップで済ませるための簡単なラッパーを書いてみた。ものすごく単純だが用法を誤らなければ相応に使える。</p>

<pre><code class="language-brush:perl gutter:true title:fcgi.pl">use strict;  
use warnings;  
use FCGI;  
use File::Spec;  
open STDERR, "&gt;&amp;", STDOUT;  
my $request = FCGI::Request or die;  
while ( $request-&gt;Accept &gt;= 0 ) {  
    my $stderr = '';
    eval {
        local $SIG{__WARN__} = sub { $stderr .= shift };
        local $SIG{__DIE__} = sub { die($stderr = shift) };
        my $file = File::Spec-&gt;catfile(
            $ENV{DOCUMENT_ROOT} // 'Undefined',
            $ENV{SCRIPT_NAME} // 'index.fcgi'
        );
        return print "Status: 404\n\n" unless -f $file;
        do $file;
    };
    $stderr = "$@" if $@;
    if ($stderr) {
        print "Status: 403\nContent-Type: text/plain\n\n", $stderr;
    }
}
1;  
__END__  
</code></pre>

<p>このラッパーを要求パス *.fcgi に対して適用する<sup id="fnref:1"><a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fn:1" rel="footnote">1</a></sup>と、ドキュメントルート以下の FastCGIファイルについて <strong>半常駐化</strong>状態で実行されるようになる。PATH_INFO は期待どうり動作するし、 <em>既定のドキュメント</em> に index.fcgi を追加した場合にも対応する。ただしあくまでも簡易的に、だ。</p>

<p>このラッパーがなぜ半常駐化なのかというと、常駐対象になるのが、特殊変数および <code>our</code> 宣言<sup id="fnref:2"><a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fn:2" rel="footnote">2</a></sup>されたパッケージ変数と <code>use</code> または <code>require</code> で読み込まれたモジュールに限定されているからだ。.fcgi ファイル自体は <code>do $file</code> の形式で読込実行されているが、これはこの命令が実行される度にファイルアクセスが発生し、コードが再コンパイル<sup id="fnref:3"><a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fn:3" rel="footnote">3</a></sup>されることによる。このとき <code>do $file</code> は <code>package main</code> 名前空間内で実行されているため、複数の .fcgi で同名のグローバル変数や関数宣言が共有されることになる。</p>

<p>.fcgi 自体は毎回再コンパイルされるが、その中で使われる use モジュールについてはコンパイル済コードが再利用されるため、その使用比率が大きいコードほどメリットがある。一方で名前空間のファイル別隔離処理を端折っているため同名関数は後続のコード実行で上書きされてしまうため注意を要する。このあたりが簡易版と称する所以だ。</p>

<p>エラー処理について言えば、FastCGI 起動前に STDERR を STDOUT にリダイレクトすることで、<code>do $file</code> 実行時の構文エラーが極力 IIS Worker Process へ渡らないように細工している。<code>$SIG{}</code> の定義も同様に STDERR を漏らさないためだ。これを怠ると IIS が <em>503 Service Unavailable</em> を返すようになり、かつ、サーバ自体を再起動しなければ回復できない<sup id="fnref:4"><a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fn:4" rel="footnote">4</a></sup>ことがままある。</p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>このラッパーそのものはドキュメントルートの外に配置する。また拡張子も.fcgi以外（.pl）とするべきだ。またこのラッパー自体は完全常駐状態となるため、リロードするためには IIS Worker Process を終了させなければならない。 <a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fnref:1" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:2"><p>our $count //= 0; などと書けば、未定義状態の初回ロード時だけ初期化される。レキシカル変数については eval $EXPR と違って異なるスコープとなる。よって my 宣言は重複しても影響がない。 <a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fnref:2" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:3"><p>require は再ロードも再コンパイルもしないが、再実行もしない。再コンパイルせずに再実行だけ行う関数はない。 <a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fnref:3" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:4"><p>IISサービス（W3SVC）を再起動するだけでは回復しない。$stderr の出力時に <em>Status: 500</em> としていないのも同じ理由による。これだけ念を入れても不用意な実行時エラー（よくあるのは再入不能モジュールの実行時エラー。例えば binmode Encoding関係や Win32関係など）でサービス停止に追い込まれる当たり、IIS の FastCGI 環境は結構繊細なので、エラーに無頓着なタイプの開発者には全く向いていない。 <a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fnref:4" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[IIS8+FastCGI+ActivePerl+Mojolicious::Lite]]></title><description><![CDATA[<p>最近のWindows IISでMojolicious::Liteを動かそうとすると、普通はCGIモードしか使えなくて全く実用的なパフォーマンスが確保できない。これは双方が互いを全くサポートしていないが故だが、これはそれをどうにかしてしまおうというメモ。</p>

<ul class="index"></ul>

<h3 id="iiscgi">IISのCGIセットアップ</h3>

<p>まず普通にIISでPerl CGIを動かすための手順。一応 Windows 10/Windows Server 2012 と ActivePerl 5.20(64bit) を前提にしているが、他の組み合わせでも大差はない。</p>

<p><strong>コントロールパネル＞プログラム＞プログラムと機能＞Windowsの機能の有効化または無効化</strong>と辿ってダイアログを開き、<strong>インターネットインフォメーションサービス</strong>にチェックを付ける。更にそのサブメニューを開いて<strong>World Wide Webサービス＞アプリケーション開発機能>CGI</strong>にチェックを付ける。最低限これだけを有効化すれば要は足りるからOKを押して変更を確定する。初回導入時についてはサーバ再起動は必要ない。<sup id="fnref:1"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:1" rel="footnote">1</a></sup></p>

<p><img src="http://multix.jp/content/images/2015/12/2015-12-27-10-48-22.png" alt=""></p>

<p>ActivePerl(64bit)が既にインストールされており、パスが通っているならコマンドプロンプトを管理者権限で開いて以下のコマンドをタイプする。</p>

<pre><code class="language-brush:plain">ap-iis-config add all --site 1 --cgi  
</code></pre>

<p>するとIISマネージャーの<strong></strong></p>]]></description><link>http://multix.jp/iis8-fastcgi-mojolicious-lite/</link><guid isPermaLink="false">51d12cb6-9839-4b14-b74d-53400cfcb7e2</guid><category><![CDATA[Windows]]></category><category><![CDATA[Web]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Sun, 27 Dec 2015 04:33:31 GMT</pubDate><content:encoded><![CDATA[<p>最近のWindows IISでMojolicious::Liteを動かそうとすると、普通はCGIモードしか使えなくて全く実用的なパフォーマンスが確保できない。これは双方が互いを全くサポートしていないが故だが、これはそれをどうにかしてしまおうというメモ。</p>

<ul class="index"></ul>

<h3 id="iiscgi">IISのCGIセットアップ</h3>

<p>まず普通にIISでPerl CGIを動かすための手順。一応 Windows 10/Windows Server 2012 と ActivePerl 5.20(64bit) を前提にしているが、他の組み合わせでも大差はない。</p>

<p><strong>コントロールパネル＞プログラム＞プログラムと機能＞Windowsの機能の有効化または無効化</strong>と辿ってダイアログを開き、<strong>インターネットインフォメーションサービス</strong>にチェックを付ける。更にそのサブメニューを開いて<strong>World Wide Webサービス＞アプリケーション開発機能>CGI</strong>にチェックを付ける。最低限これだけを有効化すれば要は足りるからOKを押して変更を確定する。初回導入時についてはサーバ再起動は必要ない。<sup id="fnref:1"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:1" rel="footnote">1</a></sup></p>

<p><img src="http://multix.jp/content/images/2015/12/2015-12-27-10-48-22.png" alt=""></p>

<p>ActivePerl(64bit)が既にインストールされており、パスが通っているならコマンドプロンプトを管理者権限で開いて以下のコマンドをタイプする。</p>

<pre><code class="language-brush:plain">ap-iis-config add all --site 1 --cgi  
</code></pre>

<p>するとIISマネージャーの<strong>サイト＞Default Web Site＞機能ビュー＞ハンドラーマッピング</strong>に以下のスクリプトマップが登録されているはずだ。</p>

<p><img src="http://multix.jp/content/images/2015/12/2015-12-27-12-17-27.png" alt=""></p>

<p>復数のサイトが作られている場合、個別にこのスクリプトマップが必要になる。前述の<mark>--site 1</mark>の数字がサイトの登録番号を示しているので、これを変更しながら必要なだけ繰り返す。あるいはこのダイアログ画像を参考に手動でスクリプトマップを追加する。</p>

<p>なお32bit版のActivePerlでは、ISAPIを使用することもできる。Windows機能ダイアログで<strong>ISAPIフィルター</strong>と<strong>ISAPI拡張</strong>にもチェックをつけていれば、以下のコマンドでISAPIのハンドラーが作成できる。<sup id="fnref:2"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:2" rel="footnote">2</a></sup></p>

<pre><code class="language-brush:plain">ap-iis-config add all --site 1 --cgi --isapi  
</code></pre>

<p>以上で <em>.pl ファイルはCGIとして認識されるようになる。</em>.cgi ファイルを扱いたい場合は手動でスクリプトハンドラーを追加しよう。<sup id="fnref:3"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:3" rel="footnote">3</a></sup></p>

<h4 id="fastcgi">FastCGIを有効化する</h4>

<p>FastCGIも同様にハンドラーマッピングを追加することで有効化できるが、ひとつのスクリプトに対してひとつのモジュールマップエントリが必要になる。例えば以下のスクリプトを登録してみよう。<sup id="fnref:4"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:4" rel="footnote">4</a></sup></p>

<pre><code class="language-brush:perl gutter:true title:count.fcgi">use strict;  
use warnings;  
use FCGI;

my $count = 0;  
my $request = FCGI::Request();  
while ( $request-&gt;Accept &gt;= 0 ) {  
    print "Content-type: text/html\r\n\r\n", ++$count;
}
</code></pre>

<p>このファイルは通常のURIで辿れる場所に存在しなければならない。つまり<em>C:¥inetpub¥wwwroot</em>以下以外にあるのならば、仮想ディレクトリパスが通っている必要がある。そうした上で次のようにモジュールマップを登録する。</p>

<p><img src="http://multix.jp/content/images/2015/12/2015-12-27-12-44-28.png" alt=""></p>

<p>要求パス欄は実ファイル名と一致<sup id="fnref:5"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:5" rel="footnote">5</a></sup>させなければならない。モジュール欄はドロップダウンからFastCgiModuleを選択する。名前欄は同一サイト内で重複しなければ自由だ。そして実行可能ファイル欄に<strong>Perlの実行フルパス名</strong>と<strong>スクリプトのフルパス名"</strong>を半角パイプ記号で続けて記入する。なぜパイプで繋げるのかは解らないがこのように書かなければならない。これで<em>OK</em>を押すとハンドラを登録してよいか警告ダイアログが出るので<em>はい</em>で応答する。</p>

<p>正しくFastCGIハンドラが登録されていれば、このスクリプトをブラウザで開けば（例えば localhost/test/count.fcgi）カウンタが表示されるだろう。Ctrl+RやF5でリロードすればカウントアップが進むはずだ。</p>

<p>なおスクリプトを書き換えたあとは、実行中のFastCGIハンドラを再起動しなければならない。手っ取り早いのはタスクマネージャのプロセス一覧から、<em>IIS Worker Process</em> を探して <em>タスクの終了</em> をさせてしまう方法だ。こうすればすぐに再度1からカウントをやり直す。</p>

<h4 id="mojoliciouslitefastcgi">Mojolicious::LiteアプリをFastCGIで実行する</h4>

<p>Mojoliciousは、その開発当初はともかく現在はIISをサポートしていない。だが前述のもっとも単純な FastCGIスクリプトを見ればわかるように、Requestループの中で普通にCGIスクリプトを利用すれば、そのままFastCGIプロセス <em>(IIS Worker Process)</em> として永続化される。かといってループ内で Mojolicious を呼び出してもそのままでは正常に動作しない。app->start が IIS/CGIであるかどうかを正しく認識できないためだ。そこで次のようなコードにする。<sup id="fnref:6"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:6" rel="footnote">6</a></sup></p>

<pre><code class="language-brush:perl gutter:true title:apps.fcgi">use strict;  
use warnings;

use Mojolicious::Lite;

plugin 'basic_auth';

my $count = 0;

get '/' =&gt; sub { $_[0]-&gt;render(text =&gt; 'Non restricted area' . ++$count); };

under sub {  
    my ($c) = shift;
    return $c-&gt;basic_auth(
        realm =&gt; sub {
            if ( @_ &amp;&amp; ( "@_" eq 'foo bar' ) ) {
                $c-&gt;req-&gt;env-&gt;{REMOTE_USER} = $_[0];
                return 1;
            }
        }
    );
};

get '/rest' =&gt; sub {  
    $_[0]-&gt;render(text =&gt; 'Hello ' . $_[0]-&gt;req-&gt;env-&gt;{REMOTE_USER} . ++$count . join(",", @ARGV));
};

if ($ENV{APP_POOL_CONFIG}) {  
    use FCGI;
    my $request = FCGI::Request();
    while ( $request-&gt;Accept &gt;= 0 ) {
        app-&gt;start("cgi");
    }
}
else {  
    app-&gt;start();
}
</code></pre>

<p>これはIIS固有の環境変数が存在するならば、FCGI::Requestループ実行に切り替えるようにしている。そうでなければ、つまりコマンドプロンプトで実行すれば、従来通りの挙動となる。こうしておいたうえで、FastCGIハンドラには次のように指定する。</p>

<pre><code class="language-brush:plain">C:¥Perl64¥bin¥perl.exe|C:¥test¥apps.fcgi cgi -m production  
</code></pre>

<p>ここでCGI形式で実行することを強要し、かつproductionモードを強制する。このモード指定がないと developmentモードで実行されることになり、標準出力に非HTTPプロトコルな出力が混じってしまうので、結果IISはInternal Server Errorを返すことになってしまう。</p>

<p>apps.fcgiのある場所に logディレクトリを掘っておくとそこにログが吐き出されるので、モード指定がなくても一見問題が解決できるように見える。だが実運用段階ではこのログを定期的に削除する必要に迫られるので、ここではその方法は取らない。ログファイルは IIS Worker Process が握っているので、このプロセスを終了させないとログファイルを削除することができないのだ。他には IISの環境変数に MOJO_MODE を追加して回避する手段があるが、今回は設定が一箇所ですむ手法を取り上げた。<sup id="fnref:7"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:7" rel="footnote">7</a></sup></p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>機能を無効化した際はアンインストールのために再起動が必要になる。 <a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fnref:1" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:2"><p>64bit版ではISAPIはサポートされなくなった。もっともこれを使う機会じたいが滅多にないので、困る／困ったという話もおよそ聞いたことがない。 <a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fnref:2" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:3"><p>Strawberry Perlや Cygin Perl、その他CGIで使えるものはなんであれこのように手動で登録する。 <a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fnref:3" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:4"><p>mod_rewrite的な仮想パス機能ではないので、/などを指定することはできない。 <a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fnref:4" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:5"><p>FCGIモジュールは標準インストールされていないので事前に <em>ppm install FCGI</em> を実行しておく必要がある。 <a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fnref:5" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:6"><p>このサンプルはBASIC認証プラグインのテストも兼ねている。このプラグインは <em>ppm install Mojolicious-Plugin-BasicAuth</em> でインストールできる。ブラウザで apps.fcgi/rest を開くと認証ダイアログが出るのでユーザ名:foo パスワード:bar を入力してみて欲しい。 <a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fnref:6" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:7"><p>環境変数切り替えでは一見、復数のモードを切り替えて使い分けられるように見える。だがUnix環境と違ってIISマネージャをいちいち立ち上げないとその操作すら覚束ないのでメリットがない。結局必要なだけ個別のモジュールハンドラを登録してURIで使い分けたほうが簡単だったりする。 <a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fnref:7" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[LinuxヘッドレスLiveBoot（CentOS編）]]></title><description><![CDATA[<p>本稿は<a href="http://multix.jp/headless-liveboot-ubuntu/">前稿</a>の CentOS版だ。要件定義はおよそ同じだが、LiveBootの実現方法の違いにより、構築手順は大きく異なる。</p>

<ul class="index"></ul>

<hr>

<h4 id="">要件定義</h4>

<ol>
<li>対象機は x86_64（amd64）とする。  </li>
<li>VGAデバイスは物理的に搭載されていない。GUIも使用しない。よって起動する CentOSはコンソールベースとする。  </li>
<li>BIOSコンソールリダイレクト機能は対象機材でサポートされているものとする。  </li>
<li>シリアルコンソールは baud=115.2kで接続する。<sup id="fnref:1"><a href="http://multix.jp/headless-liveboot-centos/#fn:1" rel="footnote">1</a></sup>  </li>
<li>成果物は ISOイメージとし、CD-R/DVD-Rに焼いて USB-ODDブート（USB光学ドライブ起動）できるものとする。通常は、再起動毎にあらゆる設定や痕跡は完全に忘却される。  </li>
<li>他の Linuxマシンを必要とすることなく、一般的な Windowsマシンから既成の USBインストーラを用いて、USBフラッシュドライブに変換できるものとする。この際 persistent領域を設定できなければならない。  </li>
<li>yum一式を備えている。追加したパッケージや設定は、persistent有効時は維持される。  </li>
<li>既定のログインユーザ名は <em>liveuser</em> とし、パスワードも無しとする。ゆえにパスワード無しで sudoできるものとする。（一般的な</li></ol>]]></description><link>http://multix.jp/headless-liveboot-centos/</link><guid isPermaLink="false">cdc0b430-1ee4-4d3b-b6b8-e9407ad9c019</guid><category><![CDATA[てくにかるむ]]></category><category><![CDATA[Windows]]></category><category><![CDATA[Linux]]></category><category><![CDATA[めもらんだむ]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Wed, 24 Jun 2015 02:31:27 GMT</pubDate><content:encoded><![CDATA[<p>本稿は<a href="http://multix.jp/headless-liveboot-ubuntu/">前稿</a>の CentOS版だ。要件定義はおよそ同じだが、LiveBootの実現方法の違いにより、構築手順は大きく異なる。</p>

<ul class="index"></ul>

<hr>

<h4 id="">要件定義</h4>

<ol>
<li>対象機は x86_64（amd64）とする。  </li>
<li>VGAデバイスは物理的に搭載されていない。GUIも使用しない。よって起動する CentOSはコンソールベースとする。  </li>
<li>BIOSコンソールリダイレクト機能は対象機材でサポートされているものとする。  </li>
<li>シリアルコンソールは baud=115.2kで接続する。<sup id="fnref:1"><a href="http://multix.jp/headless-liveboot-centos/#fn:1" rel="footnote">1</a></sup>  </li>
<li>成果物は ISOイメージとし、CD-R/DVD-Rに焼いて USB-ODDブート（USB光学ドライブ起動）できるものとする。通常は、再起動毎にあらゆる設定や痕跡は完全に忘却される。  </li>
<li>他の Linuxマシンを必要とすることなく、一般的な Windowsマシンから既成の USBインストーラを用いて、USBフラッシュドライブに変換できるものとする。この際 persistent領域を設定できなければならない。  </li>
<li>yum一式を備えている。追加したパッケージや設定は、persistent有効時は維持される。  </li>
<li>既定のログインユーザ名は <em>liveuser</em> とし、パスワードも無しとする。ゆえにパスワード無しで sudoできるものとする。（一般的な LiveBootの流儀）  </li>
<li>既定のネットワーク設定・接続はオフとする。  </li>
<li>これらのパスワードやネットワーク設定は persistent有効時には自由に設定変更して永続化することができ、再起動しても設定内容が失われてはならない。</li>
</ol>

<p>以後は CentOS-7-x86_64-LiveCD-1503.iso をベースにして話を進める。32bit版や CentOS-6でも同様の手順で構築できる。<sup id="fnref:2"><a href="http://multix.jp/headless-liveboot-centos/#fn:2" rel="footnote">2</a></sup></p>

<p>Ubuntuでは最終的な rootfsを作るのにオンメモリ（2GiB程度）で作業できたが、CentOS用の rootfsを作成するには作業領域が足りないため、起動用 USBメモリの他に Ext4でフォーマットできるフラッシュドライブや HDDを別途用意しなければならない。なお最終的に出来上がるブータブルイメージは CD-Rに充分格納できるサイズ（640MiB以下）になる。</p>

<p>なお製作時のリファレンス機材には Riava RS670A を使用した。<sup id="fnref:3"><a href="http://multix.jp/headless-liveboot-centos/#fn:3" rel="footnote">3</a></sup></p>

<hr>

<h4 id="usb">シリアルコンソール対応のインストーラUSBメモリを作る</h4>

<p>まずヘッドレス環境で使用できる CentOSインストーラを LiveCD ISOイメージから作成する。Windows上でこれを行うには Fedora LiveUSB Creator<sup id="fnref:4"><a href="http://multix.jp/headless-liveboot-centos/#fn:4" rel="footnote">4</a></sup>を使用する。</p>

<p><img src="http://multix.jp/content/images/2015/06/2015-06-24-11-56-53.png" alt=""></p>

<p>次に起動メニューを書き換える。LiveUSB Creatorが作成したベースファイルからの相違点はシリアルコンソール設定の追加が主だ。</p>

<pre><code class="language-brush:plain gutter:true title:/syslinux/syslinux.cfg （フラッシュドライブ起動用）">#serial 0 115200
#console 0

default vesamenu.c32  
timeout 100  
menu background  
menu autoboot Starting CentOS Linux 7 in # second{,s}. Press any key to interrupt.

menu clear  
menu title CentOS Linux 7  
menu vshift 4  
menu rows 12  
menu margin 8  
menu helpmsgrow 15  
menu tabmsgrow 13

menu tabmsg Press Tab for full configuration options on menu items.  
menu separator  
menu separator  
label linux0  
  menu label ^Start CentOS Linux 7 Live
  kernel vmlinuz0
  append initrd=initrd0.img root=live:CDLABEL=LIVE rootfstype=vfat ro rd.live.image quiet rd.luks=0 rd.md=0 rd.dm=0 console=tty0 console=ttyS0,115200n8
  menu default
menu separator  
menu begin ^Troubleshooting  
  menu title Troubleshooting
label check0  
  menu label ^Test this media &amp; start CentOS Linux 7 Live
  kernel vmlinuz0
  append initrd=initrd0.img root=live:CDLABEL=LIVE rootfstype=vfat ro rd.live.image quiet rd.luks=0 rd.md=0 rd.dm=0 rd.live.check console=tty0 console=ttyS0,115200n8
menu separator  
label local  
  menu label Boot from ^local drive
  localboot 0xffff
menu separator  
label returntomain  
  menu label Return to ^main menu.
  menu exit
menu end  
</code></pre>

<p>このフラッシュドライブで、Troubleshootingサブメニュー内の <strong>Test this media &amp; start CentOS Linux 7 Live</strong> を選んで起動しよう。しばらく待っているとログインプロンプトが出現する。ここからは <code>root[ENTER]</code>で rootシェルに入ることが出来る。</p>

<hr>

<h4 id="rootfs">rootfsを作成する</h4>

<p>この時点で NetworkManager は起動しているので DHCPでのIP取得は既に済んでいるだろう。作業に必要なパッケージを yumで追加する。</p>

<pre><code class="language-brush:bash">LANG=C  
yum install -y squashfs-tools  
</code></pre>

<p>作業用の追加ドライブを接続してパーテションをフォーマットし、<code>/mnt</code>にマウントしてそこに移動する。ここでは <code>/dev/sdb1</code>とする。</p>

<pre><code class="language-brush:bash">mkfs.ext4 /dev/sdb1  
mount /dev/sdb1 /mnt  
cd /mnt  
</code></pre>

<p>起動時のフラッシュドライブは <code>/run/initramfs/live</code>に ReadOnlyでマウントされているので、これを rwオプションでリマウントして書き換え可能にする。</p>

<pre><code class="language-brush:bash">mount -o rw,remount /run/initramfs/live  
cd /run/initramfs/live  
</code></pre>

<p>現在起動している rootfsの実体は <code>/run/initramfs/live/LiveOS/squashfs.img</code>だ。このファイルを差し替えるのが最終目的だが、これは <code>LiveOS/ext3fs.img</code>という loopbackファイルを squashfsで圧縮した構造になっている。そこでこれを以下のようにして再現する。rootfs容量はここでは8GiBとするが、適宜任意のサイズに変えても良い。</p>

<pre><code class="language-brush:bash">mkdir -p squashfs-root/LiveOS  
truncate -s $((2**33)) squashfs-root/LiveOS/ext3fs.img  
mkfs.ext4 -L _CentOS-7-livecd squashfs-root/LiveOS/ext3fs.img  
</code></pre>

<p>このファイルを loopbackマウントして /mnt/chrootでアクセスできるようにする。</p>

<pre><code class="language-brush:bash">mkdir /mnt/chroot  
mount -o loop squashfs-root/LiveOS/ext3fs.img /mnt/chroot/  
</code></pre>

<p>後は yumのグループインストールコマンド一発で、任意の新しい rootfs環境が構築できる。</p>

<pre><code class="language-brush:bash">yum --installroot=/mnt/chroot --releasever=7 -y groupinstall Core  
</code></pre>

<p>releaseverオプションに指定する CentOSのバージョンは、<code>/etc/os-release</code>ファイルを参照すると良い。</p>

<hr>

<h4 id="rootfs">rootfsの環境設定</h4>

<p>まず各システム領域を chroot内に bindマウントする。そしてダミーの <code>/etc/fstab</code>（中身は空でも良い）を作成しておく。</p>

<pre><code class="language-brush:bash">mount -o bind /dev/ chroot/dev/  
mount -o bind /proc/ chroot/proc/  
mount -o bind /tmp/ chroot/tmp/  
mount -o bind /run/ chroot/run/  
touch chroot/etc/fstab  
</code></pre>

<p>initramfsの構築設定ファイルを追加する。これは LiveBoot可能な initramfsを作成するのに必須の設定だ。</p>

<pre><code class="language-brush:plain title:chroot/etc/dracut.conf.d/01-liveos.conf">hostonly="no"  
add_dracutmodules+="dmsquash-live"  
compress="xz"  
</code></pre>

<p>現在起動している LiveCDシステムの、livesysサービスファイルを chroot内にコピーしてサービス登録する。このサービスは初回起動時に <em>liveuser</em>アカウントの作成や rootパスワードの削除処理を行うようになっている。<sup id="fnref:5"><a href="http://multix.jp/headless-liveboot-centos/#fn:5" rel="footnote">5</a></sup></p>

<pre><code class="language-brush:bash">cp -p /etc/init.d/livesys* chroot/etc/init.d/  
chroot chroot/ chkconfig --add livesys  
chroot chroot/ chkconfig --add livesys-late  
</code></pre>

<p>タイムゾーンとロケール情報を設定し、NetworkManagerが初回起動時は起動しないように修正する。</p>

<pre><code class="language-brush:bash">ln -sf ../usr/share/zoneinfo/Asia/Tokyo chroot/etc/localtime

echo "LANG=en_US.utf8" &gt; chroot/etc/locale.conf

chroot chroot /usr/bin/systemctl disable NetworkManager  
</code></pre>

<hr>

<h4 id="linuxkernel">Linux kernelモジュールの組み込み</h4>

<p>kernelと、その他の追加パッケージをインストールする。</p>

<pre><code class="language-brush:bash">yum --installroot=/mnt/chroot -y install kernel squashfs-tools dump isomd5sum epel-release  
</code></pre>

<p>kernelと作成された initramfsを、フラッシュドライブに移動する。rescue関係のファイルは必要ないので削除する。</p>

<pre><code class="language-brush:bash">rm -f chroot/boot/*rescue*  
mv chroot/boot/vmlinuz-* /run/initramfs/live/syslinux/vmlinuz0.new  
mv chroot/boot/initramfs-* /run/initramfs/live/syslinux/initrd0.img.new  
</code></pre>

<p>最後にクリーンアップを行ってアンマウントする。rootfsの umount後は fsckで損傷がないかチェックする。</p>

<pre><code class="language-brush:bash highlight:[9]">yum --installroot=/mnt/chroot clean all  
find chroot/var/log -type f -exec /bin/truncate -s0 {} \;  
umount chroot/proc/  
umount chroot/dev/  
umount chroot/run/  
umount chroot/tmp/

du -sh chroot/  
720M    chroot/

umount chroot/  
fsck.ext4 squashfs-root/LiveOS/ext3fs.img  
</code></pre>

<hr>

<h4 id="squashfsimg">squashfs.img ファイルの作成</h4>

<p>こうして出来た rootfsを mksquashfsで圧縮すれば、LiveBoot可能な新しいシステムファイルになる。ただし Ext4フォーマットした loopbackファイルをそのまま圧縮するため、インストール過程で生じた削除ファイルの痕跡などもそのまま残っていて圧縮率が低下してしまう。</p>

<pre><code class="language-brush:bash highlight:[4]">mksquashfs squashfs-root squashfs.img.new

ls -lh squashfs.img.new  
-rw-r--r--. 1 root root 474M Jun 24 08:21 squashfs.img.new
</code></pre>

<p>これを可能な限り取り除いて圧縮するには、dump/restoreコマンドを経由してファイルシステムを完全に作り直すとよい。ただし dumpコマンドはブロックデバイスしか扱えないので、losetupコマンドを用いて /dev/loopX に loopbackファイルをバインドして指定する。手順は面倒だが、比較するとその効果は絶大だ。</p>

<pre><code class="language-brush:bash highlight:[11,26,27]">mv squashfs.img.new squashfs.img.nev

yum install -y dump

loopdev=$(losetup -f)  
losetup $loopdev squashfs-root/LiveOS/ext3fs.img  
dump -0z9 -f rootdump $loopdev  
losetup -d $loopdev

ls -lh rootdump  
-rw-r--r--. 1 root root 277M Jun 24 08:30 rootdump

rm -f squashfs-root/LiveOS/ext3fs.img  
truncate -s $((2**33)) squashfs-root/LiveOS/ext3fs.img  
mkfs.ext4 -L _CentOS-7-livecd squashfs-root/LiveOS/ext3fs.img

mount -o loop squashfs-root/LiveOS/ext3fs.img chroot  
(cd chroot/; restore -r -f ../rootdump)
rm -fr chroot/lost+found restoresymtable  
umount chroot

fsck.ext4 squashfs-root/LiveOS/ext3fs.img  
mksquashfs squashfs-root squashfs.img.new

ls -lh squashfs.img.ne?  
-rw-r--r--. 1 root root 474M Jun 24 08:21 squashfs.img.nev
-rw-r--r--. 1 root root 250M Jun 24 08:38 squashfs.img.new

cp squashfs.img.new /run/initramfs/live/LiveOS/  
</code></pre>

<hr>

<h4 id="rootfs">新 rootfsでの起動テスト</h4>

<p>こうして作成した rootfsファイルをブートUSBメモリにコピーして shutdownし、Windows（あるいは他のLinux機）で以下の起動用ファイルを差し替える。</p>

<ul>
<li>vmlinuz0.new を syslinux/vmlinuz0 へ</li>
<li>initrd0.img.new を syslinux/initrd0.img へ</li>
<li>squashfs.img.new を LiveOS/squashfs.img へ</li>
</ul>

<p>syslinux/syslinux.cfg は前述のそのままでよいが、今度の起動は通常の <strong>Start CentOS Linux</strong>ラベルを選択する。ログイン・プロンプトは <em>root</em>で直接、あるいは <em>liveuser</em>（いずれもパスワード無し）でシェルに入れる。<em>liveuser</em>アカウントは sudoで <em>root</em>アカウントになれる。</p>

<p><img src="http://multix.jp/content/images/2015/06/2015-06-24-16-47-35.png" alt=""></p>

<p>NetworkManagerは停止状態なので、ネットワークに接続するには dhclientで Link Upするか、nmcli/nmtuiでインタフェース設定を行って NetworkManagerを起動する。</p>

<pre><code class="language-brush:bash">sudo -s

systemctl enable NetworkManager  
systemctl start NetworkManager

nmcli connection modify enp2s0 ipv4.method manual ipv4.addresses 192.168.240.101/24 ipv4.gateway 192.168.240.1 ipv4.dns 8.8.8.8

systemctl restart network.service

# cat /etc/sysconfig/network-scripts/ifcfg-enp2s0
</code></pre>

<hr>

<h4 id="iso">ISOイメージを作成する</h4>

<p>元の ISOの squashfs.img等を差し替えた、新しいISOイメージを作成する。まず作業に必要なパッケージをインストールし、作業領域に ISO内のファイルを展開して ReadOnlyを外す。</p>

<pre><code class="language-brush:bash">mount /dev/sdb1 /mnt  
cd /mnt

# install mkisofs tools
yum install -y mkisofs isomd5sum

# mount image: CentOS-7-x86_64-LiveCD-1503.iso
mkdir loop  
mount /dev/cdrom loop

# copy iso files and modify attributes
cp -rf loop files  
chmod -R +w files/  
umount loop  
</code></pre>

<p>フラッシュドライブから先に作成した新しいシステムファイルをコピーする。このときフラッシュドライブ上では <code>/syslinux</code>に vmlinuz0と initrd0.img ファイルが置かれているが、ISOイメージでは <code>/isolinux</code>ディレクトリになる。<sup id="fnref:6"><a href="http://multix.jp/headless-liveboot-centos/#fn:6" rel="footnote">6</a></sup></p>

<pre><code class="language-brush:bash"># copy kernel and filesystem
cp -f /run/initramfs/live/syslinux/vmlinuz0 files/isolinux/  
cp -f /run/initramfs/live/syslinux/initrd0.img files/isolinux/  
cp -f /run/initramfs/live/LiveOS/squashfs.img files/LiveOS/  
</code></pre>

<p>files/isolinux/isolinux.cfg をシリアルコンソール対応に修正する。最初のシリアルポート設定は BIOSコンソールリダイレクトの有無による。また先に作成した rootfsは SELinuxをサポートするように構築してはいないので、これを無効にする。<code>isolinux/mt86plus</code>については標準では存在しないが、これについては<a href="http://multix.jp/buildup-memtest-headless/">別稿を参照</a>されたい。</p>

<pre><code class="language-brush:plain gutter:true title:isolinux/isolinux.cfg （ODD起動用）">#serial 0 115200
#console 0

default vesamenu.c32  
timeout 100  
menu background  
menu autoboot Starting CentOS Linux 7 in # second{,s}. Press any key to interrupt.

menu clear  
menu title CentOS Linux 7  
menu vshift 4  
menu rows 12  
menu margin 8  
#menu hidden
menu helpmsgrow 15  
menu tabmsgrow 13

menu tabmsg Press Tab for full configuration options on menu items.  
menu separator  
menu separator  
label linux0  
  menu label ^Start CentOS Linux 7 Live
  kernel vmlinuz0
  append initrd=initrd0.img root=live:CDLABEL=CentOS7-liveboot rootfstype=auto ro rd.live.image quiet rd.luks=0 rd.md=0 rd.dm=0 selinux=0 console=tty0 console=ttyS0,115200n8
  menu default
menu separator  
menu begin ^Troubleshooting  
  menu title Troubleshooting
label check0  
  menu label ^Test this media &amp; start CentOS Linux 7 1503 Live
  kernel vmlinuz0
  append initrd=initrd0.img root=live:CDLABEL=CentOS7-liveboot rootfstype=auto ro rd.live.image quiet rd.luks=0 rd.md=0 rd.dm=0 rd.live.check selinux=0 console=tty0 console=ttyS0,115200n8 
label memtest  
  menu label Test ^memory
  kernel mt86plus
  append console=ttyS0,115200n8
menu separator  
label local  
  menu label Boot from ^local drive
  localboot 0xffff
menu separator  
label returntomain  
  menu label Return to ^main menu.
  menu exit
</code></pre>

<p>mkisofsコマンドで ISOイメージを作成する。ここではもっともシンプルなオプション構成として GRUB（gfxboot）や EFI対応、Macintosh対応は行ってはいない。注意が必要なのは -Vオプションに渡すCDラベルで、これは kernel起動オプションに記述したのと同じものでなければならない。またこのラベルは最大16文字なのでこれを超えないようにしよう。</p>

<pre><code class="language-brush:bash">find ./files -print | xargs touch -m {} \;  
LANG=C mkisofs -r -J -l \  
  -V CentOS7-liveboot \
  -cache-inodes \
  -b isolinux/isolinux.bin \n
  -c isolinux/boot.cat \
  -no-emul-boot \
  -boot-load-size 4 \
  -boot-info-table \
  -o CentOS7-liveboot.iso \
  ./files
</code></pre>

<p>こうして出来た ISOイメージに、それ自体の改竄検出 MD5チェックサムを implantisomd5コマンドで埋め込む。Ubuntuではファイル単位で MD5をチェックしていたが、CentOSではメディアそのものをチェックする。従ってフラッシュドライブではこの仕組は動かない。一方で CD-R等に焼いた際に Write Errorが有っても、メディアの完全性を確認することが出来る。<sup id="fnref:7"><a href="http://multix.jp/headless-liveboot-centos/#fn:7" rel="footnote">7</a></sup></p>

<pre><code class="language-brush:bash">LANG=C implantisomd5 CentOS7-liveboot.iso  
</code></pre>

<p>改竄チェックは checkisomd5コマンドで行える。引数には調べたい ISOファイルか、メディアを挿れた ODDのデバイスファイルを指定すればよい。なお implantisomd5と checkisomd5コマンドは、isomd5sumパッケージに含まれている。<sup id="fnref:8"><a href="http://multix.jp/headless-liveboot-centos/#fn:8" rel="footnote">8</a></sup></p>

<pre><code class="language-brush:bash">checkisomd5 CentOS7-liveboot.iso  
checkisomd5 /dev/cdrom  
</code></pre>

<hr>

<h4 id="cdrwdvdrwiso">CD-RW/DVD-RWに ISOイメージを焼く</h4>

<p>光学メディアへのライティングは、Ubuntuとおなじく wodimコマンドで行える。</p>

<pre><code class="language-brush:bash">yum install -y wodim

# erase CD/DVD-RW media
wodim dev=/dev/sr0 blank=fast

# writing image
wodim -sao -eject dev=/dev/sr0 CentOS7-liveboot.iso

# check image
checkisomd5 /dev/sr0  
</code></pre>

<p>こうして作成した光学メディアで ODDブートが正常に行えたら、<em>Test this media &amp; start CentOS</em>ラベルで起動してみよう。検査が正常に終了するとログイン・プロンプトに進み、失敗した場合は System Haltするはずだ。<sup id="fnref:9"><a href="http://multix.jp/headless-liveboot-centos/#fn:9" rel="footnote">9</a></sup></p>

<hr>

<h4 id="persistent">persistentモードを設定する</h4>

<p>前述の ISOイメージ/光学メディアで ODDブートが正常に行えたなら、次はその ISOイメージを Live USB Creatorでもって、今度は persistent領域を設定したフラッシュドライブを作成しよう。</p>

<p>persistentモードは kernelオプションの <code>rd.live.overlay=LABEL=LIVE</code>によって有効になる。initramfsは指定された LABELを blkidコマンドで探し、これを <code>/run/initramfs/live/</code>にマウントする。persistent領域ファイルは <code>LiveOS</code>ディレクトリ内の <code>overlay-${LABEL}-${UUID}</code>というファイル名で探して、Device Mapperによって snapshotとして <code>/</code>に結合される。これ自体は初期状態では（ファイルシステムではないので）単なる Zero Fillファイルである。</p>

<pre><code class="language-brush:bash highlight:[2,5,8,9,10,11]">blkid -L LIVE  
/dev/sda1

blkid /dev/sda1  
/dev/sda1: LABEL="LIVE" UUID="0621-6D9B" TYPE="vfat" 

ls -l /run/initramfs/live/LiveOS/  
total 1299100  
-rwxr-xr-x 1 root root      20480 Jun 26 05:26 osmin.img
-rwxr-xr-x 1 root root 1068498944 Jun 30 12:32 overlay-LIVE-0621-6D9B
-rwxr-xr-x 1 root root  261758976 Jun 30 08:26 squashfs.img
</code></pre>

<p>Ubuntuと異なり、Device Mapperを使用しているためもあって実際にあとどの程度データが書き込めるか（保存できるか）は dfコマンドでは判断することができない。dmsetupコマンドの statusサブコマンドで、live-rwボリュームの snapshot消費ブロック数（512byte単位）を知ることはできるから、これを利用するしかない。</p>

<pre><code class="language-brush:bash highlight:[2,5]">dmsetup status live-rw  
0 16777216 snapshot 14960/2086912 72

echo | awk '{print 14960/2086912}'  
0.00716849  
</code></pre>

<p>また Ubuntuの場合は、persistentファイルは overlayfsで重ねられた独立したファイルシステムであったから、非起動状態のフラッシュドライブから persistent領域内のファイルを抜き出すことは容易だったが、CentOSではこれは容易なことではない。</p>

<p>（現在起動しているのとは別の）フラッシュドライブ内の persistent領域にアクセスするには、以下のようにする。</p>

<pre><code class="language-brush:bash highlight:[15,23,28]">## 作業領域の準備

cd /mnt  
mkdir media squash rootfs

## フラッシュドライブを作業領域にマウントし、その中の squashfs.imgをマウントする

mount /dev/sdc1 media  
mount -o loop media/LiveOS/squashfs.img squash

## ext3fs.img（readonly）と overlayファイルに loopbackデバイスを割り当てる
## 空きデバイス名は losetup -fで得られる

losetup -f  
/dev/loop6

losetup /dev/loop6 squash/LiveOS/ext3fs.img -r  
losetup /dev/loop7 edia/LiveOS/overlay-LIVE-0621-6D9B

## ふたつのデバイスを束ねて rootfsを再生する

blockdev -q --getsz /dev/loop6  
16777216

dmsetup create live-rootfs --table "0 16777216 snapshot /dev/loop6 /dev/loop7 P 8"

dmsetup status live-rootfs  
0 16777216 snapshot 17312/2086912 80

## あとはfsckしたり・・・

fsck -f -y /dev/mapper/live-rootfs

## dumpしたり・・・
## （squashfs.img作成以後分だけを抜き出すのに /etc/dumpdatesを利用する例）

echo "/dev/mapper/live-rootfs 0 $(LANG=C \  
  date +"%a %b %d %X %z" \
    -r media/syslinux/ldlinux.sys)" &gt;&gt; /etc/dumpdates
dump -u1z9 /dev/mapper/live-rootfs -f rootfsdump

## mountしたり・・・

mount /dev/mapper/live-rootfs rootfs  
ls -a rootfs/home/liveuser

## 作業を終えたら後始末してフラッシュドライブを切り離す

umount rootfs  
dmsetup remove live-rootfs  
losetup -d /dev/loop6  
losetup -d /dev/loop7  
umount squash  
umount media  
rmdir media squash rootfs  
</code></pre>

<hr>

<h4 id="homeswap">/homeや swapパーテションを設定する</h4>

<p><code>/etc/init.d/livesys</code>を覗いてみると解る<sup id="fnref:10"><a href="http://multix.jp/headless-liveboot-centos/#fn:10" rel="footnote">10</a></sup>が、フラッシュドライブの <code>/LiveOS</code>ディレクトリに、<code>home.img</code>ファイルがあれば <code>/home</code>に、swap.imgファイルが有れば <code>swap</code>に、それぞれ自動的にマウントするようになっている。これらのボリュームファイルは以下のようにセットアップする。</p>

<pre><code class="language-brush:bash highlight:[22,23,24,25,26,27,28,32,33,34]">## persistentオフの場合はフラッシュドライブを rwリマウントする 
mount -o rw,remount /run/initramfs/live

## 保存領域へcd
cd /run/initramfs/live/LiveOS

## home.img ボリュームの作成（サイズは適宜）
truncate -s $((2**31)) home.img  
mkfs.ext4 -L "home-vol" home.img

## swap.img ボリュームの作成（サイズは適宜）
truncate -s $((2**31)) swap.img  
mkswap -L "swap-vol" swap.img

## あとは特に設定変更もなく再起動するだけ
sync;sync;sync  
reboot

## homeボリュームの有無は losetupで確認できる
## 容量は dfコマンドで把握可能
losetup  
NAME       SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE  
/dev/loop0         0      0         0  1 /osmin.img (deleted)
/dev/loop1         0      0         0  1 /osmin
/dev/loop2         0      0         0  1 /run/initramfs/live/LiveOS/squashfs.img
/dev/loop3         0      0         0  1 /LiveOS/ext3fs.img
/dev/loop4         0      0         0  0 /LiveOS/overlay-LIVE-748B-70C6
/dev/loop5         0      0         0  0 /run/initramfs/live/LiveOS/home.img

## swapは freeコマンドで容量が確認できる
free  
              total        used        free      shared  buff/cache   available
Mem:        8162140      178452     7610852       16720      372836     7767880  
Swap:        262136           0      262136
</code></pre>

<hr>

<h4 id="">ダウンロード</h4>

<p><a href="https://secure.multix.jp/download/SerialLive/CentOS7-liveboot-20150703.iso">CentOS7-liveboot-20150703.iso</a> 313MiB (<a href="https://secure.multix.jp/download/SerialLive/CentOS7-liveboot-20150703.iso.md5">md5sum</a>)</p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>USBシリアル変換は FTDI系を前提にしている。PL2303系（特にノーブランドのコピー品）は baud=115.2kではFIFOバッファが足りずに通信不具合が多発してトラブルのもとになる。 <a href="http://multix.jp/headless-liveboot-centos/#fnref:1" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:2"><p><a href="https://www.centos.org">centos.org</a> <a href="http://multix.jp/headless-liveboot-centos/#fnref:2" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:3"><p><a href="http://store.shopping.yahoo.co.jp/riava/rs670-s16a.html">Riava RS670A</a> 基本スペックは Intel Gigabit NICx6を持つ Intel/Atom C2758 でメモリ8GiB。基本OSは CentOSまたはUbuntu。SSD内蔵だがこれはここでは使用しない（壊さない）。なおフロントパネルのシリアルコンソールポート（COM0）はCisco互換配線（<a href="http://yost.com/computers/RJ45-serial">Yost Serial Device Wiring Standard</a>）になっている。 <a href="http://multix.jp/headless-liveboot-centos/#fnref:3" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:4"><p><a href="https://fedorahosted.org/liveusb-creator/">Fedora LiveUSB Creator</a> <a href="http://multix.jp/headless-liveboot-centos/#fnref:4" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:5"><p>persistentモードで初期化済フラグファイルが書けるようになると、これらの初期化処理はスキップされるようになる。 <a href="http://multix.jp/headless-liveboot-centos/#fnref:5" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:6"><p>Fedora LiveUSB Creatorが、ISOイメージ中の /isolinux を /syslinux へコンバートしている。 <a href="http://multix.jp/headless-liveboot-centos/#fnref:6" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:7"><p>Ubuntuでは USBメモリでも一応改竄チェックが可能だが、ISOイメージ中のブートセクタといった非ファイル領域に生じたライティングエラーを検出できない。 <a href="http://multix.jp/headless-liveboot-centos/#fnref:7" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:8"><p>Dracutは、isomd5sumパッケージがインストール済なら initramfsを作成する際にこれを組み込む。initramfsに組み込まれていない場合、rd.live.checkオプションによるメディア検査は正しく実行できない。 <a href="http://multix.jp/headless-liveboot-centos/#fnref:8" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:9"><p><em>Test this media &amp; start CentOS</em>は光学メディア起動時は chkisomd5が走り、検査に成功しなければログイン・プロンプトに達しないが、USBメモリ起動時はメディア自体が検査不能なので、そのまま進んでログイン・プロンプトに達することができる。 <a href="http://multix.jp/headless-liveboot-centos/#fnref:9" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:10"><p>物理パーテションを /homeにしたり、暗号化したりする起動オプションもある。 <a href="http://multix.jp/headless-liveboot-centos/#fnref:10" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[LinuxヘッドレスLiveBoot（Ubuntu編）]]></title><description><![CDATA[<p>VGAのない産業用ヘッドレスPC類に Linux系OSを組み込むのは割とよく行う。もっぱら CentOSをメインにしているのだが、時には諸事情やら比較検証用途やらで急遽 Ubuntuが必要になったりもする。そこでまあ Ubuntuベースのヘッドレス LiveBootを作っておくことにした。  </p>

<ul class="index"></ul>  

<hr>

<h4 id="">要件定義</h4>

<ol>
<li>対象機は x86_64（amd64）とする。  </li>
<li>VGAデバイスは物理的に搭載されていない。GUIも使用しない。よって起動する Ubuntuはコンソールベースとする。  </li>
<li>BIOSコンソールリダイレクト機能は対象機材でサポートされている。  </li>
<li>シリアルコンソールは baud=115.2kで接続する。<sup id="fnref:1"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:1" rel="footnote">1</a></sup>  </li>
<li>成果物はISOイメージとし、CD-R/DVD-Rに焼いてUSB-ODDブート（USB光学ドライブ起動）できるものとする。通常は、再起動毎にあらゆる設定や痕跡は完全に忘却される。  </li>
<li>他の Linuxマシンを必要とすることなく、一般的な Windowsマシンから既成のUSBインストーラを用いて、USBフラッシュドライブに変換できるものとする。この際 persistent領域を設定できなければならない。  </li>
<li>apt-get一式を備えている。追加したパッケージや設定は、persistent有効時は維持される。  </li>
<li>既定のログインユーザ名は <em>ubuntu</em> とし、パスワードも無しとする。ゆえにパスワード無しで sudoできるものとする。（一般的なLiveBootの流儀）  </li>
<li>既定のネットワーク設定・</li></ol>]]></description><link>http://multix.jp/headless-liveboot-ubuntu/</link><guid isPermaLink="false">2367878c-5496-47cb-962a-44089346dca7</guid><category><![CDATA[てくにかるむ]]></category><category><![CDATA[Windows]]></category><category><![CDATA[Linux]]></category><category><![CDATA[めもらんだむ]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Tue, 09 Jun 2015 02:27:30 GMT</pubDate><content:encoded><![CDATA[<p>VGAのない産業用ヘッドレスPC類に Linux系OSを組み込むのは割とよく行う。もっぱら CentOSをメインにしているのだが、時には諸事情やら比較検証用途やらで急遽 Ubuntuが必要になったりもする。そこでまあ Ubuntuベースのヘッドレス LiveBootを作っておくことにした。  </p>

<ul class="index"></ul>  

<hr>

<h4 id="">要件定義</h4>

<ol>
<li>対象機は x86_64（amd64）とする。  </li>
<li>VGAデバイスは物理的に搭載されていない。GUIも使用しない。よって起動する Ubuntuはコンソールベースとする。  </li>
<li>BIOSコンソールリダイレクト機能は対象機材でサポートされている。  </li>
<li>シリアルコンソールは baud=115.2kで接続する。<sup id="fnref:1"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:1" rel="footnote">1</a></sup>  </li>
<li>成果物はISOイメージとし、CD-R/DVD-Rに焼いてUSB-ODDブート（USB光学ドライブ起動）できるものとする。通常は、再起動毎にあらゆる設定や痕跡は完全に忘却される。  </li>
<li>他の Linuxマシンを必要とすることなく、一般的な Windowsマシンから既成のUSBインストーラを用いて、USBフラッシュドライブに変換できるものとする。この際 persistent領域を設定できなければならない。  </li>
<li>apt-get一式を備えている。追加したパッケージや設定は、persistent有効時は維持される。  </li>
<li>既定のログインユーザ名は <em>ubuntu</em> とし、パスワードも無しとする。ゆえにパスワード無しで sudoできるものとする。（一般的なLiveBootの流儀）  </li>
<li>既定のネットワーク設定・接続はオフとする。  </li>
<li>これらのパスワードやネットワーク設定は persistent有効時には自由に設定変更して永続化することができ、再起動しても設定内容が失われてはならない。</li>
</ol>

<p>要するに、KVMやDocker等の仮想マシン環境のそれと同様の感覚で、気軽に使用できる使い捨て環境の物理版<sup id="fnref:2"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:2" rel="footnote">2</a></sup>である。その仕様上基本的には RAMシステムとなり Swapは初期設定しない。従ってメインメモリは充分潤沢に搭載されているものとする。<sup id="fnref:3"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:3" rel="footnote">3</a></sup></p>

<p>Ubuntu LiveCD Customization については <strong><a href="https://help.ubuntu.com/community/LiveCDCustomization">こちらを参照</a></strong> のこと。</p>

<p>以後は ubuntu-15.04-desktop-amd64.iso をベースにして話を進める。32bit版でも同様だが、14.xx LTS では本稿で解説している手法は使えない。<sup id="fnref:4"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:4" rel="footnote">4</a></sup></p>

<p>なお製作時のリファレンス機材には Riava RS670A を使用した。<sup id="fnref:5"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:5" rel="footnote">5</a></sup></p>

<hr>

<h4 id="usb">シリアルコンソール対応のインストーラUSBフラッシュドライブを作る</h4>

<p>まずはヘッドレス環境で使用できるUbuntuインストーラを作らなければならない。UbuntuのインストーラISOには Desktop版と Server版があり、後者には CLI（テキスト）インストール機能があるが、本稿では Desktop版のISOイメージをベースにする。</p>

<p>端的には、内蔵ストレージにテキストベースの Ubuntuをクリーンインストールして GRUBで起動するのであれば、Server版をベースにするのが筋だ。だが Server版のISOイメージは LiveBoot機能を持たないため、今回の目的では Desktop版ISOイメージから作り出すほうが楽なのである。</p>

<p>用意するのは Ubuntu Desktop 64-bit Install ISO ファイルと、FAT32でフォーマットした1GiB以上のUSBフラッシュドライブと、Universal USB Installer<sup id="fnref:6"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:6" rel="footnote">6</a></sup>だ。</p>

<p>Windowsマシンで USBインストーラを立ち上げ、ダウンロードしたISOイメージと書き込み先フラッシュドライブを指定して起動可能メディアを作成する。この時 persistent領域は指定しない（永続化しない）。</p>

<p><img src="http://multix.jp/content/images/2015/06/2015-06-09-11-13-58.png" alt=""></p>

<p>次にブートメニューを書き換えてシリアルコンソールが使えるようにする。この作業は原則として TeraPadや MIFESのようなテキストエディタで行うが、Windows Notepadでもできなくはない。フラッシュドライブに書き込まれた元ファイルは LF改行なので Notepadでは全部が1行に繋がってしまうが、ブートローダー（SYSLINUX）は CR+LF改行でも動作するので、Notepadでも以下のテンプレートにそのまま入れ替えてしまってもよい。</p>

<p>isolinux.cfg冒頭のシリアルポート設定<sup id="fnref:7"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:7" rel="footnote">7</a></sup>は、BIOSにコンソールリダイレクト機能が備わっている場合には必要ない。BIOSとブートローダーの両方で設定をしてしまうと、入出力がバッティングして表示が崩れたりキー入力に不具合が生じる。従って今回の場合はコメントアウトで記述しておくだけとする。</p>

<pre><code class="language-brush:plain gutter:true highlight:[1,2] title:/isolinux/isolinux.cfg">#serial 0 115200
#console 0
path  
include menu.cfg  
default vesamenu.c32  
prompt 0  
timeout 50  
</code></pre>

<p>stdmenu.cfg からはスプラッシュ画像や表示色の設定をごっそり削る。またブートメニューが 80x24程度に収まるよう、vshift等を調整する。</p>

<pre><code class="language-brush:plain gutter:true title:/isolinux/stdmenu.cfg">menu vshift 4  
menu rows 10  
menu helpmsgrow 15  
menu cmdlinerow 16  
menu timeoutrow 16  
menu tabmsgrow 18  
menu tabmsg Press ENTER to boot or TAB to edit a menu entry  
</code></pre>

<p>txt.cfgのカーネルオプション行には、BIOSからカーネルに制御が渡った後のシリアルコンソール設定 <code>console=tty0 console=ttyS0,115200n8</code>を追記する。またLiveCD機能による自動ネットワーク構成が勝手に動かないよう <code>ip=frommedia</code>も必ず設定する。そしてこの段階では GUIと Ubuntuインストーラの起動をスキップしてシェルに落ちるための <mark>single</mark>を指定しておく。</p>

<pre><code class="language-brush:plain gutter:true highlight:[6] title:/isolinux/txt.cfg （USBメモリブート用）">menu title Ubuntu Console boot menu  
default live  
label live  
  menu label ^Try Ubuntu Console
  kernel /casper/vmlinuz.efi
  append  file=/cdrom/preseed/ubuntu.seed boot=casper cdrom-detect/try-usb=true noprompt floppy.allowed_drive_mask=0 ignore_uuid initrd=/casper/initrd.lz ip=frommedia console=tty0 console=ttyS0,115200n8 single ---
label memtest  
  menu label Test ^memory
  kernel /install/mt86plus
  append console=ttyS0,115200n8
label hd  
  menu label ^Boot from first hard disk
  localboot 0x80
</code></pre>

<p>追加された kernel起動パラメータは以下の意味を持つ。</p>

<ul>
<li><em>boot=casper</em> <br>
casperによるブートシーケンス（LiveBoot）を実行する。  </li>
<li><em>cdrom-detect/try-usb=true</em> <br>
USBメモリブートデバイスを CDROMと同等に扱う。  </li>
<li><em>floppy.allowed_drive_mask=0</em> <br>
フロッピーデバイスを無視する。</li>
<li><em>ignore_uuid</em> <br>
ブートメディアのUUIDをチェックしない。</li>
<li><em>ip=frommedia</em> <br>
ブートメディアの /etc/network/interfaces 設定を有効にする。</li>
<li><em>console=tty0</em> <br>
tty0をコンソール入出力に使用する。tty0は一般に VGA表示デバイスやフレームバッファである。  </li>
<li><em>console=ttyS0,115200n8</em> <br>
ttyS0をコンソール入出力に使用する。ttyS0は一般に第一シリアルポート。通信要件は baud=115200、パリティなし、8bit幅とする。このふたつのconsole指定は、両方書く場合はこの順番（tty0、ttyS0）で書かなければならない。<sup id="fnref:8"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:8" rel="footnote">8</a></sup>  </li>
<li><em>single</em> <br>
シングルモードで起動する。ここでは Ubuntuインストーラを起動させないために指定する。</li>
</ul>

<p>なおインストールイメージに含まれる memtest86+（mt86plus）は、シリアルコンソールに対応していない。通常のVGA表示に加えてBIOSコンソール表示もするようにリビルドしたバイナリと差し替えておくと便利だ。<sup id="fnref:9"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:9" rel="footnote">9</a></sup></p>

<hr>

<h4 id="rootfs">rootfsを作成する</h4>

<p>上記のUSBメモリで起動し <strong>Try Ubuntu Console</strong>を実行するとシングルユーザモードのbashシェルが起動する。まずは dhclientを起動したり、あるいは ifconfigと routeを叩くなりしてインターネットへ接続できるようにしよう。またロケール変数もセットする。</p>

<pre><code class="language-brush:bash">dhclient eth0

LANG=C  
</code></pre>

<p>現在起動しているこの環境は RAMシステムのため、8GiBの物理メモリを積んでいるなら 4GiB容量の RAMディスクが使える。これから作る rootfsは 2GiBも余裕があれば充分なので、/root以下を作業領域とする。なお /tmpは tmpfsマウント時のオプションのせいでそのままでは使用できない<sup id="fnref:10"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:10" rel="footnote">10</a></sup>。RAM容量が足りない場合には Ext4フォーマットした別の USBメモリや外付けHDD等を作業領域に使おう。</p>

<p>新しい rootfsは debootstrapパッケージを利用して作成する。さらにそれを LiveBootで使えるようにするために squashfs-toolsパッケージを用いるので、これらをインストールする。</p>

<pre><code class="language-brush:bash">apt-get install debootstrap squashfs-tools  
</code></pre>

<p>構築する Ubuntuのコードネームと CPUアーキテクチャは USBメモリから起動したそれと同じものとするが、これらは以下のコマンドで確認できる。</p>

<pre><code class="language-brush:plain highlight:[2,3,4,5,6,9]">lsb_release -a  
No LSB modules are available.  
Distributor ID: Ubuntu  
Description:    Ubuntu 15.04  
Release:        15.04  
Codename:       vivid

dpkg --print-architecture  
amd64  
</code></pre>

<p><em>lsb_release</em>コマンドが参照している実体は<code>/etc/lsb-release</code>なので、これをsourceコマンドで読めば環境変数で参照できるようになる。</p>

<pre><code class="language-brush:bash highlight:[2,3,4,5,10]">cat /etc/lsb-release  
DISTRIB_ID=Ubuntu  
DISTRIB_RELEASE=15.04  
DISTRIB_CODENAME=vivid  
DISTRIB_DESCRIPTION="Ubuntu 15.04"

source /etc/lsb-release

echo $DISTRIB_CODENAME  
vivid  
</code></pre>

<p>これらを debootstrapコマンドの引数に記述してミニマムな新規 rootfsを構築する。</p>

<pre><code class="language-brush:bash">source /etc/lsb-release  
mkdir newroot  
debootstrap --arch $(dpkg --print-architecture) \  
   $DISTRIB_CODENAME newroot http://archive.ubuntu.com/ubuntu
</code></pre>

<hr>

<h4 id="rootfs">rootfsの環境設定</h4>

<p>構築された rootfsは上記のコマンドだけで、すでに chrootして一応 apt-getも実行できる状態になっているが、kernelはまだインストールされていない。そこでこれらを行う前の準備として /proc、/dev、/run を rootfs内にマウントする。またデフォルトロケールとタイムゾーンをセットする。</p>

<pre><code class="language-brush:bash">mount -o bind /proc/ newroot/proc/  
mount -o bind /dev/ newroot/dev/  
mount -o bind /run/ newroot/run/

chroot newroot/ /usr/sbin/locale-gen en_US.UTF-8  
chroot newroot/ /usr/sbin/update-locale LANG=en_US.UTF-8  
chroot newroot/ /usr/sbin/dpkg-reconfigure locales  
chroot newroot/ /usr/bin/timedatectl set-timezone Asia/Tokyo  
</code></pre>

<p>さらに aptが参照する <code>newroot/etc/apt/sources.list</code>には必要最小限の1行しか書かれていないので、これを以下の内容に書き換える。<sup id="fnref:11"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:11" rel="footnote">11</a></sup></p>

<pre><code class="language-brush:plain title:newroot/etc/apt/sources.list for 15.04">deb http://jp.archive.ubuntu.com/ubuntu/ vivid main restricted  
deb-src http://jp.archive.ubuntu.com/ubuntu/ vivid main restricted  
deb http://jp.archive.ubuntu.com/ubuntu/ vivid-updates main restricted  
deb-src http://jp.archive.ubuntu.com/ubuntu/ vivid-updates main restricted  
deb http://jp.archive.ubuntu.com/ubuntu/ vivid universe  
deb-src http://jp.archive.ubuntu.com/ubuntu/ vivid universe  
deb http://jp.archive.ubuntu.com/ubuntu/ vivid-updates universe  
deb-src http://jp.archive.ubuntu.com/ubuntu/ vivid-updates universe  
deb http://jp.archive.ubuntu.com/ubuntu/ vivid multiverse  
deb-src http://jp.archive.ubuntu.com/ubuntu/ vivid multiverse  
deb http://jp.archive.ubuntu.com/ubuntu/ vivid-updates multiverse  
deb-src http://jp.archive.ubuntu.com/ubuntu/ vivid-updates multiverse  
deb http://jp.archive.ubuntu.com/ubuntu/ vivid-backports main restricted universe multiverse  
deb-src http://jp.archive.ubuntu.com/ubuntu/ vivid-backports main restricted universe multiverse  
deb http://security.ubuntu.com/ubuntu vivid-security main restricted  
deb-src http://security.ubuntu.com/ubuntu vivid-security main restricted  
deb http://security.ubuntu.com/ubuntu vivid-security universe  
deb-src http://security.ubuntu.com/ubuntu vivid-security universe  
deb http://security.ubuntu.com/ubuntu vivid-security multiverse  
deb-src http://security.ubuntu.com/ubuntu vivid-security multiverse  
</code></pre>

<p>次に LiveBoot専用の管理者ユーザ <em>ubuntu</em>およびグループを作成し、パスワードなしでログイン出来るようにする。さらにrootアカウントでは直接ログイン出来ないようにロックしておく。このユーザ名は LiveBoot機能を提供している casperモジュールの設定に合わせたものだ。むろんそれぞれに任意のパスワードを設定しても構わない。（それを忘れたりしなければ）</p>

<pre><code class="language-brush:bash">chroot newroot/ /usr/sbin/addgroup --system --gid=999 ubuntu  
chroot newroot/ /usr/sbin/useradd -s /bin/bash -g ubuntu --uid=999 -m -k /dev/null ubuntu  
chroot newroot/ /usr/bin/passwd -d ubuntu  
chroot newroot/ /usr/sbin/usermod -L root  
</code></pre>

<p>内蔵ストレージに直接起動可能な rootfsをインストールする場合は、この時点で<code>/etc/fstab</code>を作成しなければならないが、LiveBootではこれが起動中に動的に作成・設定されるため、必要ない。</p>

<p>以上の環境設定が済んだら、apt-getで update/upgradeを行って chroot環境を更新する。</p>

<pre><code class="language-brush:bash">chroot newroot/ /usr/bin/apt-get update  
chroot newroot/ /usr/bin/apt-get upgrade  
</code></pre>

<hr>

<h4 id="linuxkernel">Linux kernelモジュールの組み込み</h4>

<p>いよいよ rootfs内に kernelモジュールをインストールするのだが、その前に現在起動中の USBメモリ（この例では/dev/sda）の MBRセクタを一応バックアップしておく。kernelインストール中に grub-pcセットアップが走るのだが、このとき MBRが上書きされてリブート不能<sup id="fnref:12"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:12" rel="footnote">12</a></sup>になるのを避けるためだ。なおインストールする kernelパッケージ名は 64bitと 32bitでは異なる。</p>

<pre><code class="language-brush:bash">dd if=/dev/sda of=mbr count=1  
</code></pre>

<pre><code class="language-brush:bash title:64bit kernel for EFI Support">chroot newroot/ /usr/bin/apt-get install linux-signed-image-$(uname -r)  
</code></pre>

<pre><code class="language-brush:bash title:32bit kernel">chroot newroot/ /usr/bin/apt-get install linux-image-$(uname -r)  
</code></pre>

<p>grub-pcセットアップは <em>Continue without installing GRUB?</em>を選んで何もせずに抜ける。そして忘れないうちにバックアップしておいた MBRを書き戻す。</p>

<pre><code class="language-brush:bash">dd if=mbr of=/dev/sda  
</code></pre>

<p>続いてその他のパッケージを追加インストールし、整合性を確認したのち、パッケージキャッシュを削除する。少なくとも <code>plymouth</code>はインストールが必要である。</p>

<pre><code class="language-brush:bash">chroot newroot/ /usr/bin/apt-get install plymouth squashfs-tools openssh-client  
chroot newroot/ /usr/bin/apt-get -f install  
chroot newroot/ /usr/bin/apt-get upgrade  
chroot newroot/ /usr/bin/apt-get autoremove  
chroot newroot/ /usr/bin/apt-get clean  
</code></pre>

<p>最後に newroot/var以下のクリーニング<sup id="fnref:13"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:13" rel="footnote">13</a></sup>と、newroot/bootから <strong>kernelとinitrdの削除</strong>する。LiveBootではブートデバイスの <code>casper</code>ディレクトリにある kernelとinitrdをそのまま使用するのでこれらの容量を節約する。</p>

<pre><code class="language-brush:bash">find newroot/var/log -type f -exec truncate -s 0 {} \;  
find newroot/var/cache -name \*-old -delete  
rm -fr newroot/var/lib/apt/lists/*

rm -f newroot/boot/vmlinuz-* newroot/boot/initrd.img-*  
</code></pre>

<p>以上で rootfsの構築は完了したのだが、作業時にマウントした /proc、/dev、/run がアンマウントできない場合がある。その際は lsofコマンドで掴んでいるプロセス探して killすればよい。正しくアンマウントできていれば duコマンドで使用容量が把握できる。</p>

<pre><code class="language-brush:bash highlight:[10]">lsof | grep newroot

kill &lt;PID&gt;

unmount newroot/run  
unmount newroot/dev  
unmount newroot/proc

du -sh newroot/  
420M    newroot/  
</code></pre>

<hr>

<h4 id="filesystemsquashfs">filesystem.squashfs ファイルの作成</h4>

<p>構築した rootfsを mksquashfsコマンドで圧縮する。このファイルはUSBメモリの /casperディレクトリに格納するが、これは <code>/cdrom/casper</code>として見ることが出来る。ただし <code>/cdrom</code>（USBメモリ）は roマウントされていてそのままでは書き込めないため、rwオプションでリマウントする。またそこにある <code>filesystem.squashfs</code>はいままさに <code>/rofs</code>にマウントして使われているため、直接上書きしてはならない。そこで別のファイル名で保存しておく。またこの rootfs全体のブロックサイズも保存しておく。</p>

<pre><code class="language-brush:bash">mount -o rw,remount /cdrom  
mksquashfs newroot /cdrom/casper/filesystem.squashfs.newroot  
printf $(du -s --block-size=1 newroot | cut -f1) &gt; /cdrom/casper/filesystem.size.newroot  
</code></pre>

<p>ちなみに <code>mksquashfs</code>で作成したファイルは <code>unsquashfs</code>コマンドで展開することが出来る。</p>

<pre><code class="language-brush:bash highlight:[4,5]">unsquashfs -d expand-root /cdrom/casper/filesystem.squashfs

ls expand-root/  
bin   dev  home        lib    media  opt   root  sbin  sys  usr  vmlinuz  
boot  etc  initrd.img  lib64  mnt    proc  run   srv   tmp  var  
</code></pre>

<hr>

<h4 id="rootfs">新 rootfs での起動テスト</h4>

<p>これらの作業を終えたら poweroff（shutdown -h）して、USBメモリを Windowsマシンに戻して以下の修正を行う。</p>

<ul>
<li>既存の /casper/filesystem.squashfs を filesystem.squashfs.org にリネーム</li>
<li>/casper/filesystem.squashfs.newroot を filesystem.squashfs にリネーム</li>
<li>同様に filesystem.size も差し替える。</li>
<li>/isolinux/txt.cfg の append行からキーワード single を削除する。</li>
</ul>

<p>この新たな環境での LiveBoot起動に失敗した場合は、上記の逆の手順でインストーライメージのシングルモードに戻す。そして unsquashfsコマンドで rootfsを展開して修正することを繰り返す。</p>

<p>新たな LiveBootが正しく起動してログイン・プロンプトが現れたら、ユーザ名 <em>ubuntu</em>（パスワード無し）でコンソールに入れるはずだ。また <code>sudo -s</code>で rootシェルになれる。</p>

<p><img src="http://multix.jp/content/images/2015/06/2015-06-22-18-14-35.png" alt=""></p>

<hr>

<h4 id="iso">ISOイメージを作成する</h4>

<p>こうして作成した filesystem.squashfsと、ブートメニューファイルを差し替えた ISOイメージを作成する。</p>

<p>まずベースになる Desktop版ISOイメージをローカルに展開する。これはODD（光学ドライブ）から読みだしても良いし、フラッシュドライブにISOイメージをコピーしておいて loopbackマウントで読みだしても良い。コピーしたファイルは書き込み不可となっているので <code>chmod</code>コマンドでパーミッションを変更する。</p>

<pre><code class="language-brush:bash highlight:[6,13,14]">cd /tmp

mkdir loop

sudo mount -o loop ubuntu-15.04-desktop-amd64.iso loop/  
mount: /dev/loop2 is write-protected, mounting read-only

cp -r loop files  
sudo umount loop  
chmod -R +w ./files

ls files/  
autorun.inf  casper  EFI      isolinux    pics  preseed             ubuntu  
boot         dists   install  md5sum.txt  pool  README.diskdefines  wubi.exe  
</code></pre>

<p>現在起動している filesystem.squashfsは <code>/cdrom/casper/</code>以下に見えているのでこれをコピーする。</p>

<pre><code class="language-brush:bash">cat /cdrom/casper/filesystem.squashfs &gt; files/casper/filesystem.squashfs  
</code></pre>

<p>isolinux/isolinux.cfg<sup id="fnref:14"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:14" rel="footnote">14</a></sup>や stdmenu.cfgについては前に上げたのと同じなので割愛する。新しい ODDブート用の isolinux/txt.cg は以下のように記述する。</p>

<pre><code class="language-brush:plain gutter:true title:isolinux/txt.cfg （ODDブート用）">menu title Ubuntu Console boot menu  
default live  
label live  
  menu label ^Try Ubuntu Console
  kernel /casper/vmlinuz.efi
  append  file=/cdrom/preseed/ubuntu.seed boot=casper initrd=/casper/initrd.lz quiet ip=frommedia console=tty0 console=ttyS0,115200n8 ---
label check  
  menu label ^Check disc for defects
  kernel /casper/vmlinuz.efi
  append  boot=casper integrity-check initrd=/casper/initrd.lz quiet console=tty0 console=ttyS0,115200n8 ---
label memtest  
  menu label Test ^memory
  kernel /install/mt86plus
  append console=ttyS0,115200n8
label hd  
  menu label ^Boot from first hard disk
  localboot 0x80
</code></pre>

<p>md5sum.txtは2番目の起動選択 <strong>Check disc for defects</strong>を選んだ際に参照される改竄チェックファイルだ。単純にこのファイルをゼロバイトに切り詰めれば何もチェックされない。一方この機能を活かしたい場合、チェックファイルを作成した後に md5sumが変わってしまうファイル、すなわち md5sum.txt自身と mkisofsコマンドが作成・更新する boot.cat、isolinux.bin はチェックリストから除外しておかなければならない。</p>

<pre><code class="language-brush:bash">pushd ./files  
rm -f md5sum.txt isolinux/boot.cat  
find . -type f -print | xargs md5sum | grep -v isolinux.bin &gt; ../md5sum.txt  
mv ../md5sum.txt .  
popd  
</code></pre>

<p>ISOイメージを作成する mkisofsコマンドは、同名のパッケージでインストールできる。</p>

<pre><code class="language-brush:bash">sudo apt-get install mkisofs  
</code></pre>

<p>mkisofsコマンドの引数<sup id="fnref:15"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:15" rel="footnote">15</a></sup>のうち -b（ブートローダーファイル）と-c（カタログファイル）は構築対象ディレクトリを基準とした場合のパス指定になるので注意しよう。また出力する ISOファイルは、先に上げた USBインストーラを使用する場合はその制限によりファイル名中に <em>desktop</em>の文字列が入っていなければならない。ともかく指定するオプションが多いのでシェルスクリプトにしておいたほうがコードの再利用と修正はしやすい。</p>

<pre><code class="language-brush:bash highlight:[14,15]">find ./files -print | xargs touch -m {} ¥;  
LANG=C mkisofs -r -J -l \  
  -V UBUNTU64 \
  -cache-inodes \
  -b isolinux/isolinux.bin \
  -c isolinux/boot.cat \
  -no-emul-boot \
  -boot-load-size 4 \
  -boot-info-table \
  -o ubuntu-15.04-desktopless-amd64.iso \
  ./files

ls -la ubuntu-15.04-desktop*  
-rwxr--r-- 1 ubuntu ubuntu 1150844928 Jun 17 16:59 ubuntu-15.04-desktop-amd64.iso
-rw-rw-r-- 1 ubuntu ubuntu  238929920 Jun 17 17:25 ubuntu-15.04-desktopless-amd64.iso
</code></pre>

<p>出来上がった ISOイメージはフラッシュドライブや NASなどにコピーして保存しておこう。</p>

<hr>

<h4 id="cdrwdvdrwiso">CD-RW/DVD-RWに ISOイメージを焼く</h4>

<p>前項で作成したISOイメージを Linuxで CD-RW/DVD-RWメディアに焼くには以下のようにする。</p>

<p>まずライティングソフトとして wodimパッケージをインストールする。</p>

<pre><code class="language-brush:bash">sudo apt-get install wodim  
</code></pre>

<p>USB-ODDは、おそらく /dev/sr0 等に見えるだろう。wodimコマンドの <code>-scanbus</code>オプションは SCSIバス以外には機能しないので、dmesgでデバイスノードを確認する。</p>

<pre><code class="language-brush:bash highlight:[2,3,4,5]">sudo wodim dev=/dev/sr0 --devices  
wodim: Overview of accessible drives (1 found) :  
-------------------------------------------------------------------------
 0  dev='/dev/sr0'      rwrw-- : 'Slimtype' 'eTAU108   1'
-------------------------------------------------------------------------
</code></pre>

<p>CD-RW/DVD-RWメディアをブランク消去するには wodimコマンドの blankオプションを使う。</p>

<pre><code class="language-brush:bash">sudo wodim dev=/dev/sr0 blank=fast  
</code></pre>

<p>wodimコマンドにISOイメージファイルを与えると、それを指定のドライブに焼きこむ。</p>

<pre><code class="language-brush:bash">sudo wodim -sao -eject dev=/dev/sr0 ubuntu-15.04-desktopless-amd64.iso  
</code></pre>

<hr>

<h4 id="persistent">persistentモードを設定する</h4>

<p>前述の ISOイメージ/光学メディアで ODDブートが正常に行えたなら、次はその ISOイメージを USBインストーラでもって、今度は persistentモードを設定した USBメモリを作成しよう。</p>

<p>persistentモードが有効な USBメモリは、kernel起動オプションに <em>persistent</em>が加わり、USBメモリのルートに <code>casper-rw</code>というファイルが作成される。このファイルの実体は overlayfsを用いて <code>/</code>に重ねられた ext4フォーマットの loopbackマウントファイルだ。<code>/rofs</code>にマウントされた filesystem.squashfsとの変更差分は、この <code>casper-rw</code>に記録されるようになる。</p>

<pre><code class="language-brush:plain highlight:[2,3,4,5,8,9,10,11,12]">df -Th  
Filesystem     Type      Size  Used Avail Use% Mounted on  
udev           devtmpfs  3.9G     0  3.9G   0% /dev  
tmpfs          tmpfs     798M  8.6M  789M   2% /run  
/dev/sda       vfat      2.0G  1.5G  468M  76% /cdrom
/dev/loop0     squashfs  182M  182M     0 100% /rofs
/cow           overlay   976M   14M  895M   2% /
tmpfs          tmpfs     3.9G     0  3.9G   0% /dev/shm  
tmpfs          tmpfs     5.0M     0  5.0M   0% /run/lock  
tmpfs          tmpfs     3.9G     0  3.9G   0% /sys/fs/cgroup  
tmpfs          tmpfs     3.9G     0  3.9G   0% /tmp  
tmpfs          tmpfs     798M     0  798M   0% /run/user/999  
</code></pre>

<p>persistentモードが正しく効いているかどうかは、更新したログインパスワードやネットワーク設定が再起動しても正しく機能するか否かで容易にわかる。例えばrootアカウントでログイン可能にし、ubuntuアカウントを無効にして新たにadminユーザを作成し、rootとadminにパスワードを設定してみよう。</p>

<pre><code class="language-brush:plain highlight:[6,7,8,13,14,19,20,21]">sudo -s

usermod -U root

passwd root  
Enter new UNIX password:  
Retype new UNIX password:  
passwd: password updated successfully

usermod -L ubuntu

addgroup --system admin  
Adding group `admin' (GID 112) ...  
Done.

useradd -s /bin/bash -g admin -m -k /dev/null admin

passwd admin  
Enter new UNIX password:  
Retype new UNIX password:  
passwd: password updated successfully  
</code></pre>

<p>/etc/network/interfaces に記述したネットワーク設定も persistentモードでなら起動時に反映される。<sup id="fnref:16"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:16" rel="footnote">16</a></sup></p>

<pre><code class="language-brush:plain gutter:true title:/etc/network/interfaces sample">source-directory /etc/network/interfaces.d

auto lo  
iface lo inet loopback

auto eth0  
iface eth0 inet static  
    address 192.168.240.135
    network 192.168.240.0
    netmask 255.255.255.0
    broadcast 192.168.240.255
    gateway 192.168.240.1
    dns-nameservers 192.168.240.1 8.8.8.8 8.8.4.4

auto eth4  
iface eth4 inet dhcp  
</code></pre>

<p>persistentモードで記録された内容を無視してデフォルト状態で起動するには、ブートメニューの起動ラベル選択時に <code>persistent</code>ラベルを取り除けば良い。あるいは casper-rwファイルをリネームして再起動してしまう。<sup id="fnref:17"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:17" rel="footnote">17</a></sup></p>

<pre><code class="language-brush:bash">cd /cdrom  
mv casper-rw casper-rw.bak  
reboot  
</code></pre>

<p>新たな（まっさらの）1GiBの casper-rwファイルは Linux上では以下の手順で作成できる。また事のついでなので、Windows上からもこのファイルを容易に置き換えられるように、フォーマットしたばかりの casper-rwファイルを zipコマンドで圧縮バックアップしておくと、USBインストーラを使わなくても最初期化できるので、いざというとき何かと便利だ。<sup id="fnref:18"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:18" rel="footnote">18</a></sup></p>

<pre><code class="language-brush:bash highlight:[8]">cd /cdrom

rm -f casper-rw.bak

truncate -s $((2**30)) casper-rw

ls -l casper-rw  
-rwxr-xr-x 1 root root 1073741824 Jun 18 13:11 casper-rw

mkfs.ext4 -L casper-rw casper-rw


apt-get install zip

zip casper-rw.zip casper-rw  
</code></pre>

<p>なお FAT32での最大のファイルサイズは 4GiB-1バイトなので、truncateコマンドでの容量指定は<code>$((2**32-1))</code>となる。<sup id="fnref:19"><a href="http://multix.jp/headless-liveboot-ubuntu/#fn:19" rel="footnote">19</a></sup></p>

<hr>

<h4 id="">ダウンロード</h4>

<p><a href="https://secure.multix.jp/download/SerialLive/ubuntu-15.04-desktopless-amd64-20150703.iso">ubuntu-15.04-desktopless-amd64-20150703.iso</a> 174MiB (<a href="https://secure.multix.jp/download/SerialLive/ubuntu-15.04-desktopless-amd64-20150703.iso.md5">md5sum</a>)</p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>USBシリアル変換は FTDI系を前提にしている。PL2303系（特にノーブランドのコピー品）は baud=115.2kでは通信異常が多発してトラブルのもとになる。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:1" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:2"><p>ヘッドレスにしてディスクレスでもある。PXEBOOTなら USBメモリすら使わない完全ディスクレスだが、ネットワーク等の周辺環境規模が大きくなるので気軽にお試し出来るものではない。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:2" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:3"><p>メインメモリは最低でも2GiB、標準的には8GiBは乗っているものとする。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:3" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:4"><p><a href="http://www.ubuntu.com/download/desktop">Ubuntu.com</a> 14.xx以前はシリアルコンソール自体に対応していない。ローカルストレージインストールの場合は /etc/init/ttyS0.confを作成すれば一応使えるようになるが、LiveBootはこの設定を壊してしまうため、15.04とおなじ挙動をさせるにはかなりの修正が必要になるため、本稿では 14.xx対応については割愛した。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:4" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:5"><p><a href="http://store.shopping.yahoo.co.jp/riava/rs670-s16a.html">Riava RS670A</a> 基本スペックは Intel Gigabit NICx6を持つ Intel/Atom C2758 でメモリ8GiB。基本OSは CentOSまたは Ubuntu。SSD内蔵だがこれはここでは使用しない（壊さない）。なおフロントパネルのシリアルコンソールポート（COM0）は Cisco互換配線（<a href="http://yost.com/computers/RJ45-serial">Yost Serial Device Wiring Standard</a>）になっている。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:5" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:6"><p><a href="http://www.pendrivelinux.com/universal-usb-installer-easy-as-1-2-3/">Universal USB Installer</a> <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:6" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:7"><p><a href="http://www.tldp.org/HOWTO/Remote-Serial-Console-HOWTO/configure-boot-loader-syslinux.html">Remote Serial Console HOWTO</a> <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:7" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:8"><p>最初に書いたほうが主コンソール <em>/dev/console</em>になり、そのデバイスは常に存在することが期待される。物理的にVGAを持たない機器では tty0なしの ttyS0だけでも実は良いのだが、シリアルポート未接続であったりシリアルポートドライバに問題がある場合は起動不能になる事態もありうる。そこで plymothを使うことでフレームバッファを用意し、tty0が必ず存在する状態をとすることで不具合を回避している。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:8" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:9"><p>ただし memtest86+のフル機能がシリアルコンソールで使えるわけではない。configメニュー表示等に不具合があるため、設定変更が難しい。このためECC関係などのオプションはビルド時に修正しておかなければならない。シリアルコンソール対応memtest86+の作成については<a href="http://multix.jp/buildup-memtest-headless/">別項を参照</a>のこと。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:9" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:10"><p>規定値でnosuid,nodev,noexecが指定されている。これらをremountで外しても良いが、最初から制限のかかっていない /rootや /homeを使うほうが手間がない。ただし後述の persistentモードが有効な場合、これらはRAMディスクではないので、<em>mount -t tmpfs none /mnt</em> といった具合に明示的に RAMディスクをマウントして使ったほうが良い。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:10" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:11"><p>デフォルトの1行だけでも必要最小限のことはできるが、docker等いまどきのアプリケーションをフルに動かせるようにするには、これだけの行数が必要になる。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:11" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:12"><p>MBRが壊れるのは、起動したUSBメモリ以外の内蔵ストレージがある場合でかつ /dev、/run等のマウントを忘れていた場合に発生する。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:12" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:13"><p>ログファイルは単純な削除でも良いが、wtmpやlastlog等も区別なく初期化するために、ここではtruncateでゼロバイトに切り詰めている。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:13" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:14"><p>ISOからコピーしたオリジナルのisolinux.cfgには最後に<strong>gfxboot</strong>にチェインする行があるが本稿では使わない（ISOLINUXをブートローダーとする）ためこれは必ず削除しなければ起動不能になる。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:14" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:15"><p>gfxboot関係については、GRUB設定ごと割愛した。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:15" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:16"><p>ただし kernel起動時に <em>ip=frommedia</em> をつけておかないと、ネットワーク設定は綺麗サッパリ無視されてしまうので気をつけよう。知らないと確実にハマる。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:16" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:17"><p>persistentモードで起動しているときは当然マウントされているので削除したりdd初期化したりするのは論外だが、FAT32フォーマットの USBメモリで起動している場合はマウントしたまま mvコマンドでリネームすることはできてしまう。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:17" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:18"><p>ISOイメージを生成する前に準備しておくとより効果的だろう。 <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:18" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:19"><p>ブランクファイルを用意するのに ddコマンドがよく使われるが、バイト単位でのファイルサイズ指定は不得手なので truncateコマンドを使うほうが楽だし、そもそも動作が早い。あえて ddコマンドのオプションで 4GiB-1を表現するなら <em>bs=$((2**16-1)) count=$((2**16+1))</em> となる。  <a href="http://multix.jp/headless-liveboot-ubuntu/#fnref:19" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[ModefierKeyを取得する]]></title><description><![CDATA[<p><a href="https://ghost.multix.jp/dmm-playing-viewer/">艦板-KanPan-</a>では起動時にShiftキー/Ctrlキーの押し下げ状態を見てキャッシュやCookieを消去しつつ起動できたりする。この動作はもともとOptionキーを押しながらクリックしたり実行したりすると振る舞いが変わるiTunesやAirMac Utilityに触発された小技だ・・・が、ではどうやってこれらのModefierKey状態を取得する？</p>

<ul class="index"></ul>

<hr>

<h4 id="osx">OSXの場合</h4>

<p>OSXの場合、AppKit/Frameworkの<code>NSEvent-&gt;modifierFlags</code>を利用することでModefierKeyのビットフラグ値<sup id="fnref:1"><a href="http://multix.jp/get-modefierkey-propety/#fn:1" rel="footnote">1</a></sup>を取得することが出来る。OSX付属のPerlやPythonにはAppKitのバインディングが標準でインストールされているため、例えばPerlでは以下のようにすればビットフラグ値を標準出力へ返すことが出来る。コメント他を除けば実質5行だ。</p>

<pre><code class="language-brush:perl gutter:true title:modefierkey.pl">#!/usr/bin/perl

=comment=
enum {  
   NSAlphaShiftKeyMask  = 1 &lt;&lt; 16,
   NSShiftKeyMask       = 1 &lt;&lt; 17,
   NSControlKeyMask     = 1 &lt;&lt; 18,
   NSAlternateKeyMask   = 1 &lt;&lt; 19,
   NSCommandKeyMask     = 1 &lt;&lt; 20,</code></pre>]]></description><link>http://multix.jp/get-modefierkey-propety/</link><guid isPermaLink="false">9097cc8e-f724-4a61-8eae-f8c98cafd3f7</guid><category><![CDATA[てくにかるむ]]></category><category><![CDATA[Windows]]></category><category><![CDATA[Machintosh]]></category><category><![CDATA[Perl]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Thu, 07 May 2015 08:17:04 GMT</pubDate><content:encoded><![CDATA[<p><a href="https://ghost.multix.jp/dmm-playing-viewer/">艦板-KanPan-</a>では起動時にShiftキー/Ctrlキーの押し下げ状態を見てキャッシュやCookieを消去しつつ起動できたりする。この動作はもともとOptionキーを押しながらクリックしたり実行したりすると振る舞いが変わるiTunesやAirMac Utilityに触発された小技だ・・・が、ではどうやってこれらのModefierKey状態を取得する？</p>

<ul class="index"></ul>

<hr>

<h4 id="osx">OSXの場合</h4>

<p>OSXの場合、AppKit/Frameworkの<code>NSEvent-&gt;modifierFlags</code>を利用することでModefierKeyのビットフラグ値<sup id="fnref:1"><a href="http://multix.jp/get-modefierkey-propety/#fn:1" rel="footnote">1</a></sup>を取得することが出来る。OSX付属のPerlやPythonにはAppKitのバインディングが標準でインストールされているため、例えばPerlでは以下のようにすればビットフラグ値を標準出力へ返すことが出来る。コメント他を除けば実質5行だ。</p>

<pre><code class="language-brush:perl gutter:true title:modefierkey.pl">#!/usr/bin/perl

=comment=
enum {  
   NSAlphaShiftKeyMask  = 1 &lt;&lt; 16,
   NSShiftKeyMask       = 1 &lt;&lt; 17,
   NSControlKeyMask     = 1 &lt;&lt; 18,
   NSAlternateKeyMask   = 1 &lt;&lt; 19,
   NSCommandKeyMask     = 1 &lt;&lt; 20,
   NSNumericPadKeyMask  = 1 &lt;&lt; 21,
   NSHelpKeyMask        = 1 &lt;&lt; 22,
   NSFunctionKeyMask    = 1 &lt;&lt; 23,
   NSDeviceIndependentModifierFlagsMask  = 0xffff0000U 
};
=cut=

use strict;  
use warnings;  
use Foundation;

@NSEvent::ISA = qw(PerlObjCBridge);

my $AppKit = '/System/Library/Frameworks/AppKit.framework';  
NSBundle-&gt;bundleWithPath_($AppKit)-&gt;load;

print +(NSEvent-&gt;modifierFlags);  
exit 0;

__END__  
</code></pre>

<p>Bashスクリプト等からModefierKey状態を扱いたい場合はこのスクリプトコマンドを呼び出せば充分用が足りる。</p>

<pre><code class="language-brush:bash title:ビット列を16進数表示する">#/bin/bash
printf '%x¥n' $(modefierkey.pl)  
</code></pre>

<pre><code class="language-brush:bash title:ビットを判定する">#/bin/bash
(($(modefierkey.pl) &amp;&amp; 2**17)) &amp;&amp; echo ShiftDown || echo ShiftUp
</code></pre>

<hr>

<h4 id="windows">Windowsの場合</h4>

<p>前述のコードを Windows Visual Basic<sup id="fnref:2"><a href="http://multix.jp/get-modefierkey-propety/#fn:2" rel="footnote">2</a></sup> に転写すると次のようになる。OSXで言うcommandキー、fnキーに相当するものはないが、代わりにScrollLockキーを取得することが出来る。</p>

<pre><code class="language-brush:vb gutter:true title:modefierkey.vb">Module Module1

    Sub Main()

        'OSX Cocoa
        'enum {
        '   NSAlphaShiftKeyMask  = 1 &lt;&lt; 16,
        '   NSShiftKeyMask       = 1 &lt;&lt; 17,
        '   NSControlKeyMask     = 1 &lt;&lt; 18,
        '   NSAlternateKeyMask   = 1 &lt;&lt; 19,
        '   NSCommandKeyMask     = 1 &lt;&lt; 20,
        '   NSNumericPadKeyMask  = 1 &lt;&lt; 21,
        '   NSHelpKeyMask        = 1 &lt;&lt; 22,
        '   NSFunctionKeyMask    = 1 &lt;&lt; 23,
        '   NSDeviceIndependentModifierFlagsMask  = 0xffff0000U 
        '};

        Dim intMask As Integer = 0

        If My.Computer.Keyboard.CapsLock Then
            intMask += 1 &lt;&lt; 16
        End If
        If My.Computer.Keyboard.ShiftKeyDown Then
            intMask += 1 &lt;&lt; 17
        End If
        If My.Computer.Keyboard.CtrlKeyDown Then
            intMask += 1 &lt;&lt; 18
        End If
        If My.Computer.Keyboard.AltKeyDown Then
            intMask += 1 &lt;&lt; 19
        End If
        If My.Computer.Keyboard.NumLock Then
            intMask += 1 &lt;&lt; 21
        End If
        If My.Computer.Keyboard.ScrollLock Then
            intMask += 1 &lt;&lt; 24
        End If

        Console.Write(intMask)
    End Sub

End Module  
</code></pre>

<hr>

<h4 id="nwjs">Nw.jsの場合</h4>

<p>そもそもコンソールプラットフォームであるNode.jsではModefierKeyを取得する手段は用意されていない<sup id="fnref:3"><a href="http://multix.jp/get-modefierkey-propety/#fn:3" rel="footnote">3</a></sup>が、Nw.jsではGUIウィンドウが存在するのでそちらのwindow.eventからAlt/Ctrl/Shiftの3種類のキー状態真偽値を取得することが出来る。ChromiumベースのGUIプラットフォームなのだからこれは出来て当然だ。</p>

<pre><code class="language-brush:javascript">&amp;lt;script language="javascript"&amp;gt;
switch (true) {  
  case window.event.altKey   : console.log('altKey');   break;
  case window.event.ctrlKey  : console.log('ctrlKey');  break;
  case window.event.shiftKey : console.log('shiftKey'); break;
}
&amp;lt;/script&amp;gt;
</code></pre>

<p>ただし注意が必要なのは、ウィンドウ本体を示すwindowグローバルオブジェクトはGUI内の<code>&lt;script&gt;</code>タグでロードされたコードのほうにしか継承されていない点<sup id="fnref:4"><a href="http://multix.jp/get-modefierkey-propety/#fn:4" rel="footnote">4</a></sup>だ。つまりpackage.jsonの<code>node-main</code>でバックグラウンドロードされたNode.jsネイティブなコードの方からは直接使えない。また当然のことながらウィンドウ表示前・初期化前にはこれらのキー状態を取得出来ないという制約もある。故に「Altキーを押しながらドロップダウンメニューを開いた場合は内容をかえる」といった使いドコロになる。</p>

<hr>

<h4 id="">艦板ではどうしたか</h4>

<p>艦板では結局GUI初期化前にModefierKey状態をしたかったので、外部コマンドを <code>child_process.execSync()</code> で呼び出して結果を得るようになっている。ちなみにこの時の動作はWindows版の場合；</p>

<ul>
<li>ModefierKeyを調べて押されていなければそのまま起動継続する</li>
<li>ModefierKeyが押されていた場合でも、キャッシュクリア済であったならそのまま起動継続する</li>
<li>キャッシュクリアされていなかったら外部キャッシュクリアコードにexecして自分は終了する</li>
<li>外部キャッシュクリアコード<sup id="fnref:5"><a href="http://multix.jp/get-modefierkey-propety/#fn:5" rel="footnote">5</a></sup>は自分でModefierKeyを再確認し、キャッシュフォルダを掃除したあとに艦板をexecして自身は終了する</li>
</ul>

<p>・・・などと存外面倒なことをやっている。</p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>キーとビット値の対応とcocoaでの名称はコメントを参照して頂きたいが、AlphaShiftKeyはcaps/CapsLockキー、AlternateKeyはalt/option/appleキーのことである。 <a href="http://multix.jp/get-modefierkey-propety/#fnref:1" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:2"><p>要コンパイル。VS Express版だと for Desktopエディションが必要。 <a href="http://multix.jp/get-modefierkey-propety/#fnref:2" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:3"><p>プラットフォーム別に全く異なる実装をビルドする必要があるし、そもそもNode.jsの主戦場はCLIやサーバホスティング用途なので、ModefierKeyを見る要件は殆ど無い。なのでこれが必要になるのはNw.jsのようなGUIフレームワークと組み合わせた場合に概ね限られるといえる。 <a href="http://multix.jp/get-modefierkey-propety/#fnref:3" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:4"><p>親ウィンドウが生成される前から動き出すインスタンスなので継承しようがないとも言える。だが単に継承されていないから使えないだけなので、知ってるほう(GUI)が知らないほう(Native)に module.exports 経由で教えてやれば利用できる。同様にNw.jsのguiオブジェクトも教えてやればバックグラウンド側から各種ウィンドウ操作ができる。 <a href="http://multix.jp/get-modefierkey-propety/#fnref:4" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:5"><p>Windows版同梱のWinRapper.exeがそれ。OSXのほうは Info.plistに登録された Bashシェル (launch.sh) が modefierkey.pl を呼び出し、必要ならキャッシュ削除処理をしてから Nw.js を起動する段取りだからずっとシンプルだ。 <a href="http://multix.jp/get-modefierkey-propety/#fnref:5" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[Node.js環境を作る]]></title><description><![CDATA[<p>プラットフォーム別のNode.js環境構築メモ</p>

<ul class="index"></ul>

<hr>

<h4 id="centos6linux">CentOS6/Linux</h4>

<p>CentOSの場合、nodejsとnpmの各パッケージはEPELレポジトリに収録されている。EPELを使用可能にするには<a href="https://ghost.multix.jp/centos-addon-repos/">こちら</a>を参照のこと。これが済んでいればyumだけで基本環境のインストールが出来る。</p>

<pre><code class="language-brush:plain">sudo yum install nodejs npm  
</code></pre>

<p>ただしこれでOSにインストールされるのはやや古いバージョンである。</p>

<pre><code class="language-brush:plain">$ node -v
v0.10.33  
$ npm -v
1.3.6  
</code></pre>

<p>異なるバージョンのNode.jsが必要な場合はnvmを使用して切り替える。まずnvmコマンドをgitでダウンロードし、添付のスクリプトを実行して導入する。<sup id="fnref:1"><a href="http://multix.jp/install-nodejs/#fn:1" rel="footnote">1</a></sup></p>

<pre><code class="language-brush:plain">$ git clone https://github.com/creationix/nvm.git ~/.nvm
Initialized empty Git repository in ~/.nvm/.git/  
remote: Counting objects:</code></pre>]]></description><link>http://multix.jp/install-nodejs/</link><guid isPermaLink="false">f570ed62-aba5-4a83-81be-788b686230cb</guid><category><![CDATA[Windows]]></category><category><![CDATA[Machintosh]]></category><category><![CDATA[Linux]]></category><category><![CDATA[めもらんだむ]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Thu, 07 May 2015 08:14:34 GMT</pubDate><content:encoded><![CDATA[<p>プラットフォーム別のNode.js環境構築メモ</p>

<ul class="index"></ul>

<hr>

<h4 id="centos6linux">CentOS6/Linux</h4>

<p>CentOSの場合、nodejsとnpmの各パッケージはEPELレポジトリに収録されている。EPELを使用可能にするには<a href="https://ghost.multix.jp/centos-addon-repos/">こちら</a>を参照のこと。これが済んでいればyumだけで基本環境のインストールが出来る。</p>

<pre><code class="language-brush:plain">sudo yum install nodejs npm  
</code></pre>

<p>ただしこれでOSにインストールされるのはやや古いバージョンである。</p>

<pre><code class="language-brush:plain">$ node -v
v0.10.33  
$ npm -v
1.3.6  
</code></pre>

<p>異なるバージョンのNode.jsが必要な場合はnvmを使用して切り替える。まずnvmコマンドをgitでダウンロードし、添付のスクリプトを実行して導入する。<sup id="fnref:1"><a href="http://multix.jp/install-nodejs/#fn:1" rel="footnote">1</a></sup></p>

<pre><code class="language-brush:plain">$ git clone https://github.com/creationix/nvm.git ~/.nvm
Initialized empty Git repository in ~/.nvm/.git/  
remote: Counting objects: 3595, done.  
remote: Compressing objects: 100% (20/20), done.  
remote: Total 3595 (delta 7), reused 0 (delta 0), pack-reused 3574  
Receiving objects: 100% (3595/3595), 765.49 KiB | 304 KiB/s, done.  
Resolving deltas: 100% (2060/2060), done.

$ source ~/.nvm/nvm.sh
</code></pre>

<p>nvmコマンドはエイリアスとしてシェル環境変数に組み込まれるため、新たな端末を開く度にnvm.shを再実行する必要がある。毎回使用するなら<code>~/.bashrc</code>等に追記しておくと良い。</p>

<p>初期状態ではsystemインストールされたNode.jsが使われているので<code>nvm current</code>を実行すると<em>system</em>が返る。</p>

<pre><code class="language-brush:plain">$ nvm current
system

$ which node
/usr/bin/node
</code></pre>

<p>取り敢えず最新のNode.jsを使えるようにするには次のようにするだけで良い</p>

<pre><code class="language-brush:plain">$ nvm install 0
######################################################################## 100.0%
Now using node v0.12.2 (npm v2.7.4)

$ nvm current
v0.12.2

$ node -v
v0.12.2

$ which node
~/.nvm/versions/node/v0.12.2/bin/node
</code></pre>

<p>上記のようにnvmでインストールされるnodeコマンドは、ユーザ環境毎に独立している。環境切替はシェル環境変数に依るので、systemサービスレベルでNode.jsを使用する場合は注意しなければならない。</p>

<p>なおsystemおよびnvmで導入したnodeコマンドと各グローバルnode_modulesパスは次のようになる。これはつまり<code>npm install -g</code>で書き換えられる先がsudo不要になるということでもある。</p>

<pre><code class="language-table">|＼|system|nvm|
|-:|:-|:-|
|node|/usr/bin/node|~/.nvm/versions/node/[VER]/bin/node|
|npm|/usr/bin/npm|~/.nvm/versions/node/[VER]/bin/npm|
|modeules|/usr/lib/node_modules|~/.nvm/versions/node/[VER]/lib/node_modules|
</code></pre>

<hr>

<h4 id="macosx">Mac OSX</h4>

<p>Mac OSXの場合、<a href="http://nodejs.org/">公式サイト</a>からインストーラパッケージをダウンロードしてきて実行するのがもっとも手軽だろう。だがnpmコマンドで各モジュールをインストールする段階でネイティブビルド環境（Xcode）を要求されたりすると途端に面倒なことになり、最終的な手間は以下のどれをとっても結局は大差なくなる。</p>

<ol>
<li><a href="http://nodejs.org/">公式サイト</a>からインストーラを得て導入（/usr/local/bin/node）  </li>
<li><a href="http://nodejs.org/">公式サイト</a>またはGitHubからソースを得て./configure &amp;&amp; make  </li>
<li>Mac Ports環境で port install nodejs（/opt/local/bin/node）</li>
</ol>

<p>1番以外は結局Xcodeがなければ話にならない。なおXcode（それ自体はAppStoreから入手できる）のコマンドライン・デベロッパ・ツールの追加インストールは以下のコマンドをターミナルで実行する。<sup id="fnref:2"><a href="http://multix.jp/install-nodejs/#fn:2" rel="footnote">2</a></sup></p>

<pre><code class="language-brush:plain">$ xcode-select --install
</code></pre>

<p>nvmでのバージョン切替はLinuxと同じなので前項を参照のこと。</p>

<hr>

<h4 id="windows">Windows</h4>

<p>Windowsの場合はだいたい以下のシナリオがある。</p>

<ol>
<li><a href="http://nodejs.org/">公式サイト</a>からインストーラを得て導入  </li>
<li><a href="https://www.visualstudio.com/downloads/download-visual-studio-vs">Visual Studio</a>（Expressの場合はfor Web）に<a href="https://nodejstools.codeplex.com">NTVSアドオン</a>を追加する  </li>
<li>CygwinあるいはMinGW環境でGitHunソースからmakeする  </li>
<li><a href="https://github.com/marcelklehr/nodist">nodist</a>で導入する</li>
</ol>

<p>基本は1番だが、おすすめはWindowsらしい強力なIDEと一体化できる2番を更に追加した環境だ。開発PCでは1+2番、サービスサーバでは1番だけという使い分けが良いだろう。3番は余程の物好きでない限りおすすめしかねる。4番はもはやふるいやり方だがZIP配布なのでレジストリを汚すこともなくコンパクトな環境を作れるが環境変数設定等は全て手動なのでGUIインストーラを使えない場合の代替手段と見たほうが良い。<sup id="fnref:3"><a href="http://multix.jp/install-nodejs/#fn:3" rel="footnote">3</a></sup> <sup id="fnref:4"><a href="http://multix.jp/install-nodejs/#fn:4" rel="footnote">4</a></sup></p>

<p>nvmでのnodeバージョン管理は<a href="https://github.com/hakobera/nvmw">nvmw</a>と<a href="https://github.com/coreybutler/nvm-windows">nvm-windows</a>の2通りの実装がある。 <br>
前者はPython2.7+が更に必要なので、ActivePython等のインストールが要求される。後者ならスタンドアロンインストーラ配布なので手軽だ。</p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>git自体はCentOSの場合は標準レポジトリに属しているので、もしなくても<strong>yum install git</strong>で導入できる。 <a href="http://multix.jp/install-nodejs/#fnref:1" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:2"><p>実行するにはかつてはApple Developer Member登録が必須だったが、いまはそんなことはない？ <a href="http://multix.jp/install-nodejs/#fnref:2" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:3"><p>自分の場合最初はnodistで環境構築したため、便宜上追記した。 <a href="http://multix.jp/install-nodejs/#fnref:3" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:4"><p>その後マルチプラットフォーム対応の Visual Studio Codeが公開されたため、環境構築の選択肢は更に増えた。 <a href="http://multix.jp/install-nodejs/#fnref:4" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[Finder/Explorerのアイコン表示]]></title><description><![CDATA[<p>MacOSの.icnsファイルもWindowsの.icoファイルも復数のアイコンリソースを含むマルチ解像度対応画像アーカイブだといえる。ではどんな状況でどの解像度のアイコン画像が選択されるのかを調べてみた。</p>

<ul class="index"></ul>

<hr>

<h4 id="osxmarvericksyosemite">OSX(Marvericks/Yosemite)の場合</h4>

<p>まずはOSXでの話。テスト用には前掲<a href="http://multix.jp/create-osx-icns-for-windows/">【OSX用アイコンファイルをWindows上で作る】</a>の<code>mkosxicns.pl</code>を使って<strong><a href="https://secure.multix.jp/download/multisize.icns">multisize.icns</a></strong>という.icnsファイルを作成した。これには以下のアイコンが含まれている。</p>

<p><img src="http://multix.jp/content/images/2015/03/2015-03-16-15-38-39.png" alt=""></p>

<p>これをファインダーで表示したりアプリケーションアイコンに設定したりしてみる。OSXはMarvericksとYosemiteを使用したがどちらも結果は変わらなかった。</p>

<pre><code class="language-table title:.icnsファイルのファインダー表示">|表示サイズ|状況|
|:-:|:-|
|16x16|リスト表示&lt;br&gt;カラム表示&lt;br&gt;デスクトップ/アイコン表示(最小)|
|32x32|デスクトップ/アイコン表示(17〜32)|
|32x32@2x&lt;br&gt;(64x64)|デスクトップ/</code></pre>]]></description><link>http://multix.jp/icon-view-size/</link><guid isPermaLink="false">101287b5-ce16-4cfa-bd70-8079bc18dc5f</guid><category><![CDATA[Windows]]></category><category><![CDATA[Machintosh]]></category><category><![CDATA[めもらんだむ]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Thu, 07 May 2015 08:13:31 GMT</pubDate><content:encoded><![CDATA[<p>MacOSの.icnsファイルもWindowsの.icoファイルも復数のアイコンリソースを含むマルチ解像度対応画像アーカイブだといえる。ではどんな状況でどの解像度のアイコン画像が選択されるのかを調べてみた。</p>

<ul class="index"></ul>

<hr>

<h4 id="osxmarvericksyosemite">OSX(Marvericks/Yosemite)の場合</h4>

<p>まずはOSXでの話。テスト用には前掲<a href="http://multix.jp/create-osx-icns-for-windows/">【OSX用アイコンファイルをWindows上で作る】</a>の<code>mkosxicns.pl</code>を使って<strong><a href="https://secure.multix.jp/download/multisize.icns">multisize.icns</a></strong>という.icnsファイルを作成した。これには以下のアイコンが含まれている。</p>

<p><img src="http://multix.jp/content/images/2015/03/2015-03-16-15-38-39.png" alt=""></p>

<p>これをファインダーで表示したりアプリケーションアイコンに設定したりしてみる。OSXはMarvericksとYosemiteを使用したがどちらも結果は変わらなかった。</p>

<pre><code class="language-table title:.icnsファイルのファインダー表示">|表示サイズ|状況|
|:-:|:-|
|16x16|リスト表示&lt;br&gt;カラム表示&lt;br&gt;デスクトップ/アイコン表示(最小)|
|32x32|デスクトップ/アイコン表示(17〜32)|
|32x32@2x&lt;br&gt;(64x64)|デスクトップ/アイコン表示(サイズ33〜64)|
|128x128|デスクトップ(最大)&lt;br&gt;アイコン表示(65〜128)|
|256x256|アイコン表示(129〜256)&lt;br&gt;Cover Flow(最小〜256)|
|512x512|アイコン表示(257〜512)&lt;br&gt;Cover Flow(257〜512)|
|512x512@2x&lt;br&gt;(1024x1024)|プレビュー&lt;br&gt;Cover Flow(513〜最大)|
</code></pre>

<p>.icnsファイルはマルチ解像度なので必要な場面でもっとも適切な解像度のアイコンが自動的に選択される。その結果7種類の画像サイズすべてが何らかの形で出現した。Retina用@2xについても特別扱いしている様子はない。またプレビューについては常に使用可能な最大解像度が選択されるがこれはプレビューの性質からして妥当だろう。</p>

<hr>

<h4 id="osx">アプリアイコン(OSX)</h4>

<pre><code class="language-table title:アプリアイコンのファインダー表示">|表示サイズ|Retina(HiDPI)|状況|
|:-:|:-:|:-|
|16x16|16x16@2x&lt;br&gt;(32x32)|リスト表示&lt;br&gt;カラム表示&lt;br&gt;デスクトップ/アイコン表示(最小)|
|32x32|32x32@2x&lt;br&gt;(64x64)|デスクトップ/アイコン表示(17〜32)|
|128x128|128x128@2x&lt;br&gt;(256x256)|ドックアイコン&lt;br&gt;デスクトップ(33〜最大)&lt;br&gt;アイコン表示(33〜128)|
|256x256|256x256@2x&lt;br&gt;(512x512)|アイコン表示(129〜256)&lt;br&gt;Cover Flow(最小〜256)&lt;br&gt;プレビュー|
|512x512|512x512@2x&lt;br&gt;(1024x1024)|アイコン表示(257〜512)&lt;br&gt;Cover Flow(257〜最大)|
</code></pre>

<p>一方この.icnsファイルをアプリアイコンに設定した場合、同一リソースなのに使用されるファイルが5種類になる。つまり標準解像度ではRetina用アイコンが、Retina環境では標準解像度アイコンが選択されない。そしてこの5種類の中に64x64の等倍は含まれていないので、その上の128x128が使用されるようになるのだ。このため.icnsファイルと並べると、ファインダー設定のアイコン表示サイズによっては見映えが変わったり両者が混在して表示されたりする。</p>

<p><img src="http://multix.jp/content/images/2015/03/2015-03-17-14-37-05.png" alt=""></p>

<hr>

<h4 id="windows81update">Windows 8.1 Updateの場合</h4>

<p>次はWindowsでの話。こちらもOSX版と同様にマルチ解像度のマルチサイズ.icoファイルを用意した。これは以下の35種類のアイコンサイズに対応する。<sup id="fnref:1"><a href="http://multix.jp/icon-view-size/#fn:1" rel="footnote">1</a></sup></p>

<p><img src="http://multix.jp/content/images/2015/03/2015-03-17-14-56-42.png" alt=""></p>

<p>.icoファイルの場合、収録可能な最大のアイコンサイズは256x256に制限される。一方で縦横サイズは任意に指定でき、実は正方形ではない長方形画像<sup id="fnref:2"><a href="http://multix.jp/icon-view-size/#fn:2" rel="footnote">2</a></sup>も登録できる。また画像形式もVista以降はαチャンネル付透過True Color PNGフォーマットが使用できるようになった。ただしPNG形式アイコンでは表示に不具合がでる場面もあるため、旧来のDIB形式の.icoファイル <strong><a href="https://secure.multix.jp/download/multisizedib.ico">multisizedib.ico</a></strong>と、PNG形式だけを集めた <strong><a href="https://secure.multix.jp/download/multisizepng.ico">multisizepng.ico</a></strong>の2種類を用意した。</p>

<p>Windowsのアイコンの扱いについてはMSDNの該当ページ<sup id="fnref:3"><a href="http://multix.jp/icon-view-size/#fn:3" rel="footnote">3</a></sup>が一次資料となるが、Explorerでは基本的には以下のようになる。</p>

<pre><code class="language-table title:Windows Exploerでのアイコンサイズ">|96dpi&lt;br&gt;100%|120dpi&lt;br&gt;125%|144dpi&lt;br&gt;150%|192dpi&lt;br&gt;200%|状況|
|16x16|←|←|←|タスクバー（小・実行アプリ）&lt;br&gt;ウィンドウタイトル|
|16x16|20x20|24x24|32x32|タスクバー（小・ピン留め・ショートカット）&lt;br&gt;リストアイテム&lt;br&gt;Explorer（小）|
|32x32|←|←|←|タスクバー（中・実行アプリ）&lt;br&gt;ダイアログアイコン|
|32x32|40x40|48x48|64x64|タスクバー（中・ピン留め・ショートカット）&lt;br&gt;Explorer(コンテンツ)|
|48x48|60x60|72x72|96x96|Explorer（中・並べて表示）|
|256x256|←|←|←|Explorer（大・特大）|
</code></pre>

<p>このうちアプリケーションを直接起動した場合のウィンドウタイトル・タスクバー・ダイアログアイコンについてはコントロールパネルの<strong>テキストとその他の項目の大きさ</strong>設定にかかわりなく常に16x16と32x32に固定<sup id="fnref:4"><a href="http://multix.jp/icon-view-size/#fn:4" rel="footnote">4</a></sup>される。また<strong>Explorer（大・特大）</strong>表示については原則として.icoファイルに収録された最大サイズのものがアイコンプレビューに選ばれる。</p>

<p>アプリケーションでもショートカットやピン留めから起動した場合、タスクバーアイコンについてはショートカットに登録されたアイコンが使用され、その場合はマルチ解像度が有効になる。</p>

<hr>

<h4 id="windows10tecnicalpreview">Windows 10 Tecnical Preview の場合</h4>

<p>ところで、Windows10はまだ開発中ということもあるが、TP版にはタスクバーのアイコン表示に問題が有る。少なくとも以下の2点が判明している。</p>

<ul>
<li>タスクバー設定が標準の場合、アイコン表示サイズが実サイズの75%に縮小される。（32x32->24x24、48x48->36x36） <br>
ただしタスクバー表示が小アイコン設定の場合の16x16表示には問題がない。</li>
<li>ショートカット・ピン留めでPNG形式アイコンを使用した場合、マスクプレーン（透過色切り抜き）が2ピクセル右にずれる。 <br>
<img src="http://multix.jp/content/images/2015/03/SS-2015-03-19-14-23-16.png" alt="96dpi" title=""> <br>
<img src="http://multix.jp/content/images/2015/03/SS-2015-03-19-14-20-53.png" alt="120dpi" title=""> <br>
左がアプリケーション直接、右がショートカットから起動した場合でいずれもPNG形式アイコンだが、右のものには黒いゴミが付いている。これはアイコンを角丸にするためのマスクプレーン（PNG画像のαチャンネル）がずれて重ねられているためだ。DBI形式の場合はこのようにはならない。<sup id="fnref:5"><a href="http://multix.jp/icon-view-size/#fn:5" rel="footnote">5</a></sup></li>
</ul>

<hr>

<h4 id="ios">iOSアプリ用アイコンの場合</h4>

<p>iOSの場合、使われる場面によってアイコンサイズが決められているため、用意する面倒を無視すれば悩む要素はない。ただしその規定サイズはiOSのバージョンによって異なり、マルチバージョン対応とする場合は当然準備しなければならない数が増える。以下はその対応一覧表だ。概ねホーム画面・設定画面・Spotlight検索<sup id="fnref:6"><a href="http://multix.jp/icon-view-size/#fn:6" rel="footnote">6</a></sup>の3場面各々に標準解像度とRetina解像度が必要になる。iOS8の場合さらに大画面iPhone用の3倍精度が追加された。</p>

<pre><code class="language-table title:iOSアプリアイコン一覧">|サイズ|iOS6|iOS7|iOS8|主な用途|
|:-|:-:|:-:|:-:|:-|
|29x29|◯|◯|◯|iPhone Spotlight(6)&lt;br&gt;iPhone設定(6)&lt;br&gt;iPad設定(78)|
|29x29@2x|◯|◯|◯|iPhone設定(67)&lt;br&gt;iPad設定(78)|
|29x29@3x|✕|✕|◯|iPhone設定(8)|
|40x40@2x|✕|◯|✕|iPhone Spotlight(7)|
|40x40@3x|✕|✕|◯|iPhone Spotlight(8)|
|50x50|◯|✕|✕|iPad Spotlight(6)&lt;br&gt;iPad設定(6)|
|50x50@2x|◯|✕|✕|iPad Spotlight(6)&lt;br&gt;iPad設定(6)|
|57x57|◯|✕|✕|iPhoneホーム(6)|
|57x57@2x|◯|✕|✕|iPhoneホーム(6)|
|60x60@2x|✕|◯|✕|iPhoneホーム(7)|
|60x60@3x|✕|✕|◯|iPhoneホーム(8)|
|72x72|◯|✕|✕|iPadホーム(6)|
|72x72@2x|◯|✕|✕|iPadホーム(6)|
|76x76|✕|◯|◯|iPadホーム(78)|
|76x76@2|✕|◯|◯|iPadホーム(78)|
|512x512@2|-|-|-|iTunes Store&lt;br&gt;App Store|
</code></pre>

<hr>

<h4 id="">ダウンロード</h4>

<ul>
<li><a href="https://secure.multix.jp/download/multisize.icns">multisize.icns</a> MacOS用</li>
<li><a href="https://secure.multix.jp/download/multisizedib.ico">multisizedib.ico</a> Windows用(DIB)</li>
<li><a href="https://secure.multix.jp/download/multisizepng.ico">multisizepng.ico</a> Windows用(PNG)</li>
</ul>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>妙に半端なサイズがやたら沢山入っているのはFavicon調査用である。ほとんどOperaのせい。 <a href="http://multix.jp/icon-view-size/#fnref:1" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:2"><p>もっとも正方形以外のアイコンリソースを想定・対応している表示環境があるとは考え難いのだが。 <a href="http://multix.jp/icon-view-size/#fnref:2" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:3"><p>Icons <a href="https://msdn.microsoft.com/en-us/library/dn742485.aspx">https://msdn.microsoft.com/en-us/library/dn742485.aspx</a> <a href="http://multix.jp/icon-view-size/#fnref:3" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:4"><p>この性質から16x16と32x32についてはPNG形式アイコンよりはDIB形式アイコンのほうが推奨される。 <a href="http://multix.jp/icon-view-size/#fnref:4" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:5"><p>カスタム表示500%拡大までの全てでゴミが付くので160pxまではDBI形式アイコンにしないと見映えが落ちてしまう。当然DBI形式では透過マスクのアンチエイリアス効果を得られないため別の方向で高品位は得られない。 <a href="http://multix.jp/icon-view-size/#fnref:5" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:6"><p>40x40と60x60の等倍スケールは使用されていないようだ。なおこの他にもUINavigationBar等で22x22の1〜3倍スケールがアプリ制作現場では必要になる。 <a href="http://multix.jp/icon-view-size/#fnref:6" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[OSX用アイコンファイルをWindows上で作る]]></title><description><![CDATA[<p>Macintosh OSX用アイコンセットをWindows上で作ってしまう方法へ至る道。OSXの.icnsファイルフォーマットについて解説する。</p>

<ul class="index"></ul>

<hr>

<h4 id="">動機</h4>

<p>艦板-KanPan- を開発するとき、最初からMacintoshとWindows両方に対応すると決めていたが、その際両者でフォーマットが異なるアイコンリソースをどうするかという問題があった。OSXでは<mark>.icns</mark>、Windowsでは<mark>.ico</mark>というファイル形式が使われる。それぞれを他方のプラットフォーム上のコマンドラインから作成できれば、make作業を一方の上で完結できる。とりあえず資料が豊富な.icoは放っておいて.icnsについて調べてみたところ意外と簡単な方法で作成できることが分かった。</p>

<hr>

<h4 id="iconutil">iconutil ユーティリティ</h4>

<p>OSX添付の純正アイコンリソース作成ユーティリティは<code>iconutil</code>という。このCLIコマンドは以下のように使用する。</p>

<ul>
<li>以下の10個のフルカラーPNG画像(RGBA)を格納した<code>foo.iconset</code>というフォルダを作成する。アイコンファイル名は厳密にこの通りであり、フォルダ名の<mark>.iconset</mark>という拡張子もこの通りでなければならない。</li>
</ul>

<pre><code class="language-table title:iconutilが要求する画像ファイル">|ファイル名|画像サイズ|
|:-|:-:|
|icon_16x16.png|16x16|
|icon_16x16@2x.png|32x32|
|icon_32x32.</code></pre>]]></description><link>http://multix.jp/create-osx-icns-for-windows/</link><guid isPermaLink="false">f88729c6-221d-4514-bf83-8a8ffb519052</guid><category><![CDATA[Windows]]></category><category><![CDATA[Machintosh]]></category><category><![CDATA[めもらんだむ]]></category><category><![CDATA[Perl]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Thu, 07 May 2015 08:11:59 GMT</pubDate><content:encoded><![CDATA[<p>Macintosh OSX用アイコンセットをWindows上で作ってしまう方法へ至る道。OSXの.icnsファイルフォーマットについて解説する。</p>

<ul class="index"></ul>

<hr>

<h4 id="">動機</h4>

<p>艦板-KanPan- を開発するとき、最初からMacintoshとWindows両方に対応すると決めていたが、その際両者でフォーマットが異なるアイコンリソースをどうするかという問題があった。OSXでは<mark>.icns</mark>、Windowsでは<mark>.ico</mark>というファイル形式が使われる。それぞれを他方のプラットフォーム上のコマンドラインから作成できれば、make作業を一方の上で完結できる。とりあえず資料が豊富な.icoは放っておいて.icnsについて調べてみたところ意外と簡単な方法で作成できることが分かった。</p>

<hr>

<h4 id="iconutil">iconutil ユーティリティ</h4>

<p>OSX添付の純正アイコンリソース作成ユーティリティは<code>iconutil</code>という。このCLIコマンドは以下のように使用する。</p>

<ul>
<li>以下の10個のフルカラーPNG画像(RGBA)を格納した<code>foo.iconset</code>というフォルダを作成する。アイコンファイル名は厳密にこの通りであり、フォルダ名の<mark>.iconset</mark>という拡張子もこの通りでなければならない。</li>
</ul>

<pre><code class="language-table title:iconutilが要求する画像ファイル">|ファイル名|画像サイズ|
|:-|:-:|
|icon_16x16.png|16x16|
|icon_16x16@2x.png|32x32|
|icon_32x32.png|32x32|
|icon_32x32@2x.png|64x64|
|icon_128x128.png|128x128|
|icon_128x128@2x.png|256x256|
|icon_256x256.png|256x256|
|icon_256x256@2x.png|512x512|
|icon_512x512.png|512x512|
|icon_512x512@2x.png|1024x1024|
</code></pre>

<ul>
<li>ターミナルで以下のように実行するとカレントディレクトリに<code>foo.icns</code>というアイコンファイルが作成される。</li>
</ul>

<pre><code class="language-brush:plain">iconutil -c icns foo.iconset  
</code></pre>

<p>10個のファイル（画像サイズ自体は7種類）を用意しろというのは御無体だが、これはまあ原画をSVG形式で作成して所定サイズとファイル名のPNG画像にコンバートすればなんとかなる。ともかく出来上がった.icnsファイルを調べてみると次のようになっていた。</p>

<hr>

<h4 id="icns">ICNSファイルフォーマット</h4>

<p>ICNSファイルは次のような構造になっている</p>

<pre><code class="language-table table:ICNSファイル構造">|:-:|
|ICNSファイルヘッダ|
|アイコンヘッダ|
|アイコンデータ|
|アイコンヘッダ|
|アイコンデータ|
|:|
</code></pre>

<p>まずファイル先頭にICNSファイルヘッダがあり、その後ろにアイコンヘッダとアイコンデータの組（ブロック）が任意個数続く。</p>

<hr>

<h4 id="icns">ICNSファイルヘッダ</h4>

<p>ICNSファイルヘッダの大きさは8バイトで、以下の2フィールドを持つ。</p>

<pre><code class="language-table table:ICNSファイルヘッダ">|Offset|Length|内容|
|-:|-:|:-|
|0|4|ファイル識別子`ICNS` (リテラルバイト文字列 0x69 0x63 0x6e 0x73)|
|4|4|ICNSファイル全体のバイナリサイズ (MSB First/Big Endian)|
</code></pre>

<p>先頭の4バイトがファイル識別子なのはMachintosh系データフォークの伝統だ。</p>

<hr>

<h4 id="">アイコンヘッダ</h4>

<p>アイコンヘッダの大きさは8バイトで、以下の2フィールドを持つ。</p>

<pre><code class="language-table title:アイコンブロックヘッダ">|Offset|Length|内容|
|-:|-:|:-|
|0|4|アイコンタイプ識別子（リテラルバイト文字列）|
|4|4|ブロック長(アイコンヘッダ＋アイコンデータの合計バイト長) (MSB First/Big Endian)|
</code></pre>

<p>アイコンタイプ識別子もまた４バイトのリテラル文字列で、これがアイコンデータの画像表示サイズと画像フォーマットとを示している。続くフィールドは後続するアイコンデータのバイナリサイズ+8に一致する。</p>

<hr>

<h4 id="">アイコンデータ</h4>

<p>これのデータフォーマットはアイコンタイプ識別子によって規定されるが、iconutilが作成するファイルの場合は<strong>PNG画像ファイルデータ</strong>そのままか<strong>RGB生データ</strong>か<strong>マスクデータ</strong>である。とにかくPNG形式のアイコン識別子が付いていたならPNGファイルがそのまま埋め込まれているので、何も難しいことはない。</p>

<hr>

<h4 id="">アイコンタイプ識別子一覧</h4>

<p>iconutilが作成するアイコンファイルに含まれるアイコン識別子の一覧を次に示す。なお識別子のレターケースは区別される。</p>

<pre><code class="language-table title:iconutilが生成するアイコンタイプ識別子一覧">|Type|Length|Size|MacOS|詳細|
|:-:|:-:|:-:|:-:|:-|
|ic07|可変|128x128|10.7+|128x128 PNG/JPEG2000|
|ic08|可変|256x256|10.5+|256x256 PNG/JPEG2000|
|ic09|可変|512x512|10.5+|512x512 PNG/JPEG2000|
|ic10|可変|1024x1024|10.7+|1024x1024 PNG/JPEG2000 (1024x1024, 512x512@2x)|
|ic11|可変|32x32|10.8+|32x32 PNG/JPEG2000 (16x16@2x)|
|ic12|可変|64x64|10.8+|64x64 PNG/JPEG2000 (32x32@2x)|
|ic13|可変|256x256|10.8+|256x256 PNG/JPEG2000 (128x128@2x)|
|ic14|可変|512x512|10.8+|512x512 PNG/JPEG2000 (256x256@2x)|
|il32|可変|32x32|8.5+|32x32 24bit TrueColor RGB|
|is32|可変|16x16|8.5+|16x16 24bit TrueColor RGB|
|l8mk|1,024|32x32|8.5+|32x32 8bit MASK|
|s8mk|256|16x16|8.5+|16x16 8bit MASK|
</code></pre>

<p>ic11からic14はRetinaディスプレイ用に追加されたもので、それぞれその半分の画像サイズのアイコンと対応している。ic10はRetina対応であると同時に<strong>OSX10.7以降のデフォルトアイコン</strong>サイズである。極端な話、このic10さえあれば、OSX10.7以降のファインダー＆ドック表示はすべて可能になる。</p>

<p>一方で以下のアイコンタイプも10.7で登場したのだが iconutil では生成されないようだ。代わりに非Retina用等倍の16x16と32x32についてはOSX10.7のものではなくMaxOS8.5のアイコンフォーマットにコンバートして収録されている。これは後方互換性を維持するためだろう。</p>

<pre><code class="language-table title:その他のアイコン識別子一覧">|Type|Length|Size|MacOS|詳細|
|:-:|:-:|:-:|:-:|:-|
|icp4|可変|16x16|10.7+|16x16 PNG/JPEG2000|
|icp5|可変|32x32|10.7+|32x32 PNG/JPEG2000|
|icp6|可変|64x64|10.7+|64x64 PNG/JPEG2000|
</code></pre>

<hr>

<h4 id="macos85">MacOS8.5形式アイコンフォーマット</h4>

<p>il32とl8mk、is32とs8mkはそれぞれアイコンカラーデータとアイコンマスクデータのペアとして使用される。常にペアであるため個々に単独の画像ファイルとしては現れず、少なくとも2ブロックから成る.icnsファイルとして存在する。</p>

<p>アイコンカラーデータは1画素あたりRGB24bitだが、RGBそれぞれ別の(8bpp)プレーン毎に、連続する左上原点ビットマップデータ列をPackBitsしてまとめられている。</p>

<pre><code class="language-table title:il32アイコンブロック (32x32画素カラー)">|Offset|Length|詳細|
|-:|-:|:-|
|0|4|アイコンタイプ識別子`il32` (0x69 0x6c 0x33 0x32)|
|4|4|ブロック長 (MSB First)|
|8|可変|Rプレーン1,024バイトのPackBitsデータ|
|?|可変|Gプレーン1,024バイトのPackBitsデータ|
|?|可変|Bプレーン1,024バイトのPackBitsデータ|
</code></pre>

<pre><code class="language-table title:is32アイコンブロック (16x16画素カラー)">|Offset|Length|詳細|
|-:|-:|:-|
|0|4|アイコンタイプ識別子`is32` (0x69 0x73 0x33 0x32)|
|4|4|ブロック長 (MSB First)|
|8|可変|Rプレーン256バイトのPackBitsデータ|
|?|可変|Gプレーン256バイトのPackBitsデータ|
|?|可変|Bプレーン256バイトのPackBitsデータ|
</code></pre>

<p>アイコンマスクデータも同様に1画素あたり8bit(8bpp)の連続する左上原点ビットマップデータ列だが、無圧縮のベタデータとして定義されている。</p>

<p>これはPNG画像フォーマットのアルファチャンネルと実質同じもので、アイコン画像の背景デスクトップからの切り抜きとアンチエイリアス表現を実現する。 なお画像縦横サイズについてはアイコンタイプ識別子で一意に決定されるためデータブロック内には現れない。</p>

<pre><code class="language-table title:l8mkマスクブロック (32x32画素マスク)">|Offset|Length|詳細|
|-:|-:|:-|
|0|4|アイコンタイプ識別子`l8mk` (0x6c 0x38 0x6d 0x6b)|
|4|4|ブロック長 (MSB First)|
|8|1,024|MASKプレーンの8bppビットマップデータ|
</code></pre>

<pre><code class="language-table title:s8mkマスクブロック  (16x16画素マスク)">|Offset|Length|詳細|
|-:|-:|:-|
|0|4|アイコンタイプ識別子`s8mk` (0x73 0x38 0x6d 0x6b)|
|4|4|ブロック長 (MSB First)|
|8|256|MASKプレーンの8bppビットマップデータ|
</code></pre>

<hr>

<h4 id="packbits">PackBits連長圧縮アルゴリズム</h4>

<p>カラープレーンのビットマップは生データではなく、PackBitsと呼ばれる連長圧縮アルゴリズムでコンバートされている。これは古き良きMacintosh時代の、PICT (QuickDraw PICTure)ファイルフォーマットで用いられていたものだからとうぜん当時の資料を調べれば詳細を知ることが出来る。そのPackBitsデータ列は次のように解釈すれば元のビットマップデータへ復元・展開できる。</p>

<ul>
<li>ポインタ上の1バイトが128未満（MSBが0）であるならこのバイトが示す0〜127に1を加え、次のそのバイト数（1〜128バイト）を取り出してそのまま出力し、ポインタを進める。</li>
<li>ポインタ上の1バイトが128以上
（MSBが1）であるならこのバイトが示す128〜255から125を引き、取り出した次の1バイトをその回数（3〜130回）繰り返して出力し、ポインタを進める。</li>
</ul>

<p>このPackBitsの圧縮アルゴリズムを真面目に実装しようとするとそれなりに面倒だが抜け道はある。無圧縮でよしと妥協すれば、128バイト毎にバイトストリームを切り刻み、0x7Fを前置した129バイトブロックに整えて横流しすればすむのである。32x32画素プレーンと16x16画素プレーンのいずれも128バイトの整数倍バイト長なのだから全く不都合はない。32x32画素プレーンなら計8ブロック1,032バイト、16x16画素プレーンなら計2ブロック258バイトの固定長データとして処理してしまえる。</p>

<hr>

<h4 id="unosxicnspl">unosxicns.pl</h4>

<p>ここまで解ってしまえばあとはもう悩むことはない。<code>unosxicns.pl</code>は以上の情報をまとめて .icnsファイルから .png<sup id="fnref:1"><a href="http://multix.jp/create-osx-icns-for-windows/#fn:1" rel="footnote">1</a></sup>ファイルを抽出するように記述したものだ。ActivePerl(5.10+)<sup id="fnref:2"><a href="http://multix.jp/create-osx-icns-for-windows/#fn:2" rel="footnote">2</a></sup>と ImageMagick(PerlMagick)<sup id="fnref:3"><a href="http://multix.jp/create-osx-icns-for-windows/#fn:3" rel="footnote">3</a></sup>で動作する。OSX他でも MacPortsで同等の環境を揃えれば動作する。</p>

<p>使い方は引数に .icnsファイルを指定すると同名の .iconsetディレクトリを作成してその中に抽出した<code>icon_NNNxNNN.png</code>を生成、あるいは<code>-t</code>オプションを付加した場合は<code>アイコンタイプ.png</code>を生成する。前掲のアイコンタイプ以外は対応外として無視する。</p>

<pre><code class="language-brush:plain">unosxicns.pl -t foo.icns

ls foo.iconset  
ic07.png              ic08.png              ic09.png  
ic10.png              ic11.png              ic12.png  
ic13.png              ic14.png              il32.png  
is32.png  
</code></pre>

<pre><code class="language-brush:perl collapse:true gutter:true title:unosxicns.pl">#!/usr/bin/perl
# $Id: unosxicns.pl 122 2015-03-16 04:06:57Z askn $ ＵＴＦ８

use 5.010;  
use strict;  
use warnings;  
use File::Basename;  
use Image::Magick;  
use Getopt::Std;

my %iconset = (  
    ic07 =&gt; [128,  1],
    ic08 =&gt; [256,  1],
    ic09 =&gt; [512,  1],
    ic10 =&gt; [1024, 2],
    ic11 =&gt; [32,   2],
    ic12 =&gt; [64,   2],
    ic13 =&gt; [256,  2],
    ic14 =&gt; [512,  2],
    icp4 =&gt; [16,   1],
    icp5 =&gt; [32,   1],
    icp6 =&gt; [64,   1],
    is32 =&gt; [16,   0],
    il32 =&gt; [32,   0],
    s8mk =&gt; [16,   0],
    l8mk =&gt; [32,   0],
);

our($opt_t);  
getopts("t");

unless (scalar @ARGV) {  
    die "Usage: unosxicns.pl [-t] icon.icns [...]\n";
}

my %cache;  
my $dirname = dirname($0);  
foreach my $argv (@ARGV) {  
    foreach my $glob (glob $argv) {
        my $filename = $glob;
        my $basename = basename $filename, qw{.svg .png .ico .icns .icowin .iconset};
        my $folder = $basename . ".iconset";
        $filename = $basename . ".icns";
        next if $cache{$filename};
        die "file not found $filename\n" unless -f $filename;
        $cache{$filename} = 1;
        &amp;mkpict($filename, $folder);
    }
}
exit;

# アイコンブロックの抽出
sub mkpict {  
    my $filename = shift;
    my $folder = shift;
    my %maskset;
    mkdir $folder, 0777;
    my($FH, $GH, $buff);
    unless (open $FH, "&lt;", $filename) {
        die "Cant read input\n";
    }
    binmode $FH;

    # 先頭のアイコンヘッダ8バイトの取得
    die "Read error\n" unless sysread($FH, $buff, 8);
    my($filetype, $filesize) = unpack "A4N", $buff;
    die "Not ICNS format\n" unless $filetype eq "icns";
    die "File size unmatch\n" unless $filesize == -s $filename;
    say "$filename filesize $filesize";

    # 各アイコンブロックについて
    while (sysread $FH, $buff, 8) {
        my($set, $ext, $outname, $scale);

        # アイコンブロックヘッダ8バイト
        my($icontype, $blocksize) = unpack "A4N", $buff;
        my $readsize = $blocksize - 8;
        die "Format broken\n"
            if $readsize &lt; 1
                or $readsize != sysread $FH, $buff, $readsize;
        say "  type $icontype length $blocksize";

        # 未知のアイコンタイプ
        unless ($set = $iconset{$icontype}) {
            say "    undefined icon type";
            next;
        }

        # PNGまたはJPEG2000形式
        if ($scale = $set-&gt;[1]) {

            # PNGヘッダ確認
            $ext = ("\x89\x50\x4E\x47" eq substr $buff, 0, 4) ? "png" : "jp2";

            # -t付きの場合はアイコンタイプをファイル名にする
            if ($opt_t) {
                $outname = sprintf "%s.%s", $icontype, $ext;
            }

            # 倍密解像度の場合のファイル名
            elsif ($scale == 2) {
                $outname = sprintf "icon_%ux%u\@x2.%s",
                    $set-&gt;[0] &gt;&gt; 1, $set-&gt;[0] &gt;&gt; 1, $ext;
            }

            # 標準解像度のファイル名
            else  {
                $outname = sprintf "icon_%ux%u.%s",
                    $set-&gt;[0], $set-&gt;[0], $ext;
            }
            say "\t$outname size $readsize";

            # 画像データ書出
            unless (open $GH, "&gt;", "$folder/$outname") {
                die "Cant write output\n";
            }
            binmode $GH;
            print $GH $buff;
            close $GH;
            next;
        }

        # MacOS8.5 mask raw format
        if ($icontype =~ /(s8mk|l8mk)/) {
            die "Mask broken,  length missmatch\n"
                unless $set-&gt;[0] ** 2 == length $buff;
            $maskset{$icontype} = [unpack "C*", $buff];
        }

        # MacOS8.5 Color PackBits format
        if ($icontype =~ /(is32|il32)/) {
            my @data;
            my @input = unpack "C*", $buff;

            # PackBits unpack
            while (scalar @input) {
                my $code = shift @input;
                if ($code &lt; 128) {
                    push @data, splice @input, 0, $code + 1;
                }
                else {
                    my $next = shift @input;
                    push @data, ($next) x ($code - 125);
                }
            }
            die "PackBits broken,  length missmatch\n"
                unless $set-&gt;[0] ** 2 * 3 == scalar @data;
            $maskset{$icontype} = \@data;
        }
    }
    close $FH;
    &amp;mkpngicon(\%maskset, "il32", "l8mk", $folder);
    &amp;mkpngicon(\%maskset, "is32", "s8mk", $folder);
}

# RGBAからPNGファイルへの変換
sub mkpngicon {  
    my $maskset = shift;
    my $type = shift;
    my $color;
    return unless $color = $maskset-&gt;{$type};
    my $mask = shift;
    my $folder = shift;
    $mask = $maskset-&gt;{$mask};
    my $size = $iconset{$type}-&gt;[0];
    my $sqrt = $size ** 2;

    my $image = Image::Magick-&gt;new(magick=&gt;'png', size=&gt;"${size}x${size}", matte=&gt;"True");
    $image-&gt;Read("NULL:");

    for (my $y = 0; $y &lt; $size; $y++) {
        for (my $x = 0; $x &lt; $size; $x++) {
            my @pixcels;
            $pixcels[0] = $color-&gt;[$y * $size + $x]             / 255; # R
            $pixcels[1] = $color-&gt;[$y * $size + $x + $sqrt]     / 255;    # G
            $pixcels[2] = $color-&gt;[$y * $size + $x + $sqrt * 2] / 255;    # B
            $pixcels[3] = $mask ? 1.0 - $mask-&gt;[$y * $size + $x] / 255 : 0.0; # A (Opacity)
            my $err = $image-&gt;SetPixel(x=&gt;$x, y=&gt;$y, color=&gt;\@pixcels, channel=&gt;'RGBA', normalize=&gt;'True');
            say $err if $err;
        }
    }
    my $outname = sprintf "icon_%ux%u.png", $size, $size;

    # -t付きの場合はアイコンタイプをファイル名にする
    if ($opt_t) {
        $outname = sprintf "%s.png", $type;
    }

    say "\t$outname";
    my $err = $image-&gt;Write("$folder/$outname");
    die $err if $err;
}

1;  
__END__  
</code></pre>

<hr>

<h4 id="mkosxicnspl">mkosxicns.pl</h4>

<p>これでようやく本題に辿り着いた。<code>mkosxicns.pl</code> は<code>unosxicns.pl</code>とは逆に  .pngファイルから .icnsファイルを生成する。ActivePerl(5.10+)と ImageMagick(PerlMagick)の他、パスの通った場所に <code>PhantomJS</code><sup id="fnref:4"><a href="http://multix.jp/create-osx-icns-for-windows/#fn:4" rel="footnote">4</a></sup>がインストールされている必要がある。<code>iconrast.js</code>は <code>PhantomJS</code>スクリプトで <code>mkosxicns.pl</code>と同じ場所に置く。OSX他でもMacPortsで同等の環境を揃えれば動作する。</p>

<p>使い方は引数に<code>-b</code>オプションと .iconsetディレクトリを指定すると、iconutilと同等の動作を行って同名の .icnsファイルを生成する。</p>

<pre><code class="language-brush:plain">mkosxicns.pl -b foo.iconset  
</code></pre>

<p>もうひとつの使い方は<code>-m</code>オプションとSVGファイル<sup id="fnref:5"><a href="http://multix.jp/create-osx-icns-for-windows/#fn:5" rel="footnote">5</a></sup>を指定することで、iconutilが必要とする10個のPNGファイルを含む .iconsetディレクトリを自動的に生成する動作だ。</p>

<pre><code class="language-brush:plain">mkosxicns.pl -m foo.svg  
</code></pre>

<p>両者は<code>-mb</code>オプションを指定して実行することで同時に行うことが出来る。すなわち以下の場合 foo.svgファイルから foo.iconsetディレクトリを作成し foo.icnsファイルを生成する。</p>

<pre><code class="language-brush:plain">mkosxicns.pl -mb foo.svg  
</code></pre>

<pre><code class="language-brush:perl collapse:true gutter:true title:mkosxicns.pl">#!/usr/bin/perl
# $Id: mkosxicns.pl 122 2015-03-16 04:06:57Z askn $ ＵＴＦ８

use 5.010;  
use strict;  
use warnings;  
use File::Basename;  
use Image::Magick;  
use Getopt::Std;

my @fileset = (  
    [128,  "icon_128x128.png",     "ic07"],
    [256,  "icon_256x256.png",     "ic08"],
    [512,  "icon_512x512.png",     "ic09"],
    [1024, "icon_512x512\@2x.png", "ic10"],
    [32,   "icon_16x16\@2x.png",   "ic11"],
    [64,   "icon_32x32\@2x.png",   "ic12"],
    [256,  "icon_128x128\@2x.png", "ic13"],
    [512,  "icon_256x256\@2x.png", "ic14"],
    [32,   "icon_32x32.png",       "icp5"],
    [16,   "icon_16x16.png",       "icp4"],
);

our($opt_m, $opt_b);  
getopts("mb");  
unless (($opt_m || $opt_b) &amp;&amp; 1 == scalar @ARGV) {  
    die "Usage: mkosxicns.pl -mb icon[.svg] [...]\n";
}

my %cache;  
my $dirname = dirname($0);  
my $phantomjs = "phantomjs";  
my $rasterize = $dirname . "/iconrast.js";

foreach my $argv (@ARGV) {  
    foreach my $glob (glob $argv) {
        my $filename = $glob;
        my $basename = basename $filename, qw{.svg .png .ico .icns .icowin .iconset};
        my $folder = $basename . ".iconset";
        if ($opt_m) {
            $filename = $basename . ".svg";
            die "file not found $filename\n" unless -f $filename;
        }
        next if $cache{$filename};
        say $filename;
        $cache{$filename} = 1;
        &amp;mkpng($filename, $folder) if $opt_m;
        &amp;mkico($basename, $folder) if $opt_b;
    }
}
exit;

# SVGからPNGへの変換
sub mkpng {  
    my $filename = shift;
    my $folder = shift;
    mkdir $folder, 0777;
    foreach my $set (@fileset) {
        printf "%s/%s\n", $folder, $set-&gt;[1];
        system $phantomjs , $rasterize, $filename, $set-&gt;[0] , $folder . "/" . $set-&gt;[1];
    }
    say "complete";
}

# ICNSファイル作成
my @maskset;  
sub mkico {  
    my $basename = shift;
    my $folder = shift;
    my $FH;
    unless (open $FH, "&gt;", $basename . ".icns") {
        die "Cant write output\n";
    }
    binmode $FH;

    my $total = 0;
    my $data = "";
    my @iconset;
    foreach my $set (@fileset) {

        # PNGファイル読込
        my $path = $folder . "/" . $set-&gt;[1];
        my $blob;
        if (open my $FI, "&lt;", $path) {
            binmode $FI;
            $blob = join '', &lt;$FI&gt;;
            close $FI;
        }
        unless (scalar $blob) {
            die "Cant read $path\n";
        }

        # MacOS8.5形式への変換
        if ($set-&gt;[2] eq "icp4") {
            &amp;buildmaskset($path, "is32", "s8mk", 16, 1, 3);
            next;
        }
        elsif ($set-&gt;[2] eq "icp5") {
            &amp;buildmaskset($path, "il32", "l8mk", 32, 0, 2);
            next;
        }

        # アイコンタイプヘッダ8バイトの付加
        my $length = length $blob;
        $data .= pack("A4N", $set-&gt;[2], $length + 8);
        $data .= $blob;
        printf "%u %s\n", length($data), $path;
    }

    # 全アイコンブロック連結
    $data .= join "", @maskset if scalar @maskset;

    # アイコンヘッダ8バイトの付加
    my $output = pack("A4N", "icns", length($data) + 8);
       $output .= $data;
    print $FH $output;
    close $FH;
    printf "%u complete", length($output);
}

# 24bitカラーと8bitマスク作成
sub buildmaskset {  
    my $path = shift;
    my $name = shift;
    my $mask = shift;
    my $size = shift;
    my $idxc = shift;
    my $idxm = shift;

    # PNGファイルを読み込んで全ピクセルを取得
    my $image = Image::Magick-&gt;new(magick=&gt;'png');
       $image-&gt;Read($path);
       $image-&gt;Set(magick=&gt;'png');
    my @input = $image-&gt;GetPixels(x=&gt;0, y=&gt;0,
        width=&gt;$size, height=&gt;$size, map=&gt;"RGBA", normalize=&gt;"true");

    # カラープレーン・マスクプレーンに分割
    my @plane = ([], [], [], []);
    for (my $y = 0; $y &lt; $size; $y++) {
        for (my $x = 0; $x &lt; $size; $x++) {
            for (my $i = 0; $i &lt; 4; $i++) {
                push @{$plane[$i]}, int(255 * shift(@input));
            }
        }
    }

    # カラープレーンをPackBits
    my @encode = ("", "", "", pack("C*", @{$plane[3]}));
    for (my $i = 0; $i &lt; 3; $i++) {
        my $buff = pack "C*", @{$plane[$i]};
        while (length $buff) {
            $encode[$i] .= chr(127) . substr($buff, 0, 128, "");
        }
    }

    # 各データを連結してヘッダ8バイトを付加
    my $buff = join "", @encode[0..2];
    $maskset[$idxc] = pack("A4N", $name, length($buff) + 8) . $buff;
    $maskset[$idxm] = pack("A4N", $mask, length($encode[3]) + 8) . $encode[3];
}

1;  
__END__  
</code></pre>

<pre><code class="language-brush:js collapse:true gutter:true title:iconrast.js">// $Id: iconrast.js 121 2015-03-13 10:01:44Z askn $

var page = require('webpage').create(),  
    system = require('system'),
    address, output, size;

if (system.args.length != 4) {  
    console.log('Usage: rasterize.js URL size filename');
    phantom.exit(1);
}
else {  
    address = system.args[1];
    size = system.args[2];
    output = system.args[3];
    page.viewportSize = { width: size, height: size };
    page.open(address, function (status) {
        if (status !== 'success') {
            console.log('Unable to load the address!');
            phantom.exit();
        } else {
            window.setTimeout(function () {
                page.render(output);
                phantom.exit();
            }, 200);
        }
    });
}
// End of script
</code></pre>

<hr>

<h4 id="">ダウンロード</h4>

<p>本項で述べた mkosxicns.pl unosxicns.pl iconrast.js の3点セット。</p>

<p><a href="https://secure.multix.jp/download/mkosxicns.zip">mkosxicns.zip</a></p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>もしPNGヘッダではないアイコンブロックであった場合は JPEG2000と仮定して .jp2ファイルで出力する。 <a href="http://multix.jp/create-osx-icns-for-windows/#fnref:1" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:2"><p>ActivePerl <a href="http://www.activestate.com/activeperl">http://www.activestate.com/activeperl</a> <a href="http://multix.jp/create-osx-icns-for-windows/#fnref:2" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:3"><p>ImageMagick <a href="http://www.imagemagick.org">http://www.imagemagick.org</a><br>・・・正直使用している機能に対してオーバースペックなのだがMachintosh/Windows両方で動作する画像操作ライブラリは希少なので困ってしまう。 <a href="http://multix.jp/create-osx-icns-for-windows/#fnref:3" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:4"><p>PhantomJS <a href="http://phantomjs.org">http://phantomjs.org</a><br>・・・CLIで動作するWebkitヘッドレスブラウザ。ここではSVGファイルから任意画素数の透過PNG画像を生成するのに使用している。 <a href="http://multix.jp/create-osx-icns-for-windows/#fnref:4" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:5"><p>SVGファイルはWidth値とHeight値が共に無指定（つまり規定値の'100%'）である必要がある。固定値が指定されていると意図した大きさのアイコン画像とはならない。 <a href="http://multix.jp/create-osx-icns-for-windows/#fnref:5" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[艦板-KanPan-とそのバリアント]]></title><description><![CDATA[<p>艦板-Kanpan- とそのバリアントはDMMオンラインゲームを快適に利用するためのプレイングビューワです。フリーサイズウィンドウ・没入型フルスクリーン全画面表示・スクリーンショット撮影のほか、ログイン・ログアウト・キャッシュ消去といった基本的な機能を簡易な操作で扱えます。</p>

<p>現在フォローしているのは「艦これ」「刀剣乱舞」「城プロ」「しんけん！！」の4種でそれぞれのOSX版・Windows版があります。気が向けば対応可能なゲームは増えるかも知れません。</p>

<p>【2015/11/20 更新 Build 10054 頒布中】</p>

<ul class="index"></ul>

<hr>

<h4 id="">おことわり</h4>

<p>本ソフトウェアのサポートはありません・いたしません。ご意見・ご要望・質問等に応える義務も責務もありません。DMMオンラインゲームプラットフォームとしても推奨動作環境ではありません。その使用・利用は一切すべて利用者の自己判断・自己責任でお願いいたします。本アプリケーションを信用出来ない場合は当然使用すべきではありません。配布物の領布は告知なく停止することがあります。配布物の自由な改造・再配布については公共の利益となる限り制限はありませんが、不利益を意図しての悪意ある改造や配布は願い下げたく存じます。</p>

<hr>

<h4 id="">ダウンロード</h4>

<p>パッケージは各ゲームと対応プラットフォーム別になっているので必要なファイル<sup id="fnref:1"><a href="http://multix.jp/dmm-playing-viewer/#fn:1" rel="footnote">1</a></sup>をダウンロードしてください。</p>

<table><tr>  
<td width="30%"><img src="http://multix.jp/content/images/2015/03/compass.png"></td>  
<td><strong>艦板 -KanPan-</strong><br>  
「艦隊これくしょん-艦これ-」対応<br><br>
<a href="https://secure.multix.jp/download/KanPan/KanPan_OSX64-10054.dmg">Macintosh OSX用</a><br>  
<a href="https://secure.multix.jp/download/KanPan/KanPan_Win32-10054.zip">Windows用</a>  
</td></tr>  
</table>

<table><tr>  
<td width="30%"><img src="http://multix.jp/content/images/2015/03/gunbai.png"></td>  
<td><strong>刀盤</strong></td></tr></table>]]></description><link>http://multix.jp/dmm-playing-viewer/</link><guid isPermaLink="false">ea72333c-b4f8-4c7e-8c9e-ed819dafa263</guid><category><![CDATA[ゲーム]]></category><category><![CDATA[Windows]]></category><category><![CDATA[Machintosh]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Fri, 06 Mar 2015 08:06:00 GMT</pubDate><media:content url="http://multix.jp/content/images/2015/03/teitoku_1384053723673.png" medium="image"/><content:encoded><![CDATA[<img src="http://multix.jp/content/images/2015/03/teitoku_1384053723673.png" alt="艦板-KanPan-とそのバリアント"><p>艦板-Kanpan- とそのバリアントはDMMオンラインゲームを快適に利用するためのプレイングビューワです。フリーサイズウィンドウ・没入型フルスクリーン全画面表示・スクリーンショット撮影のほか、ログイン・ログアウト・キャッシュ消去といった基本的な機能を簡易な操作で扱えます。</p>

<p>現在フォローしているのは「艦これ」「刀剣乱舞」「城プロ」「しんけん！！」の4種でそれぞれのOSX版・Windows版があります。気が向けば対応可能なゲームは増えるかも知れません。</p>

<p>【2015/11/20 更新 Build 10054 頒布中】</p>

<ul class="index"></ul>

<hr>

<h4 id="">おことわり</h4>

<p>本ソフトウェアのサポートはありません・いたしません。ご意見・ご要望・質問等に応える義務も責務もありません。DMMオンラインゲームプラットフォームとしても推奨動作環境ではありません。その使用・利用は一切すべて利用者の自己判断・自己責任でお願いいたします。本アプリケーションを信用出来ない場合は当然使用すべきではありません。配布物の領布は告知なく停止することがあります。配布物の自由な改造・再配布については公共の利益となる限り制限はありませんが、不利益を意図しての悪意ある改造や配布は願い下げたく存じます。</p>

<hr>

<h4 id="">ダウンロード</h4>

<p>パッケージは各ゲームと対応プラットフォーム別になっているので必要なファイル<sup id="fnref:1"><a href="http://multix.jp/dmm-playing-viewer/#fn:1" rel="footnote">1</a></sup>をダウンロードしてください。</p>

<table><tr>  
<td width="30%"><img src="http://multix.jp/content/images/2015/03/compass.png" alt="艦板-KanPan-とそのバリアント"></td>  
<td><strong>艦板 -KanPan-</strong><br>  
「艦隊これくしょん-艦これ-」対応<br><br>
<a href="https://secure.multix.jp/download/KanPan/KanPan_OSX64-10054.dmg">Macintosh OSX用</a><br>  
<a href="https://secure.multix.jp/download/KanPan/KanPan_Win32-10054.zip">Windows用</a>  
</td></tr>  
</table>

<table><tr>  
<td width="30%"><img src="http://multix.jp/content/images/2015/03/gunbai.png" alt="艦板-KanPan-とそのバリアント"></td>  
<td><strong>刀盤 -TouBan-</strong><br>  
「刀剣乱舞」対応<br><br>
<a href="https://secure.multix.jp/download/KanPan/TouBan_OSX64-10054.dmg">Macintosh OSX用</a><br>  
<a href="https://secure.multix.jp/download/KanPan/TouBan_Win32-10054.zip">Windows用</a>  
</td></tr>  
</table>

<table><tr>  
<td width="30%"><img src="http://multix.jp/content/images/2015/03/katana.png" alt="艦板-KanPan-とそのバリアント"></td>  
<td><strong>剣番 -KenBan-</strong><br>  
「しんけん!!」対応<br><br>
<a href="https://secure.multix.jp/download/KanPan/KenBan_OSX64-10054.dmg">Macintosh OSX用</a><br>  
<a href="https://secure.multix.jp/download/KanPan/KenBan_Win32-10054.zip">Windows用</a>  
</td></tr>  
</table>

<table><tr>  
<td width="30%"><img src="http://multix.jp/content/images/2015/03/castle.png" alt="艦板-KanPan-とそのバリアント"></td>  
<td><strong>城盤 -JyouBan-</strong><br>  
「御城プロジェクト」対応<br><br>
<a href="https://secure.multix.jp/download/KanPan/JyouBan_OSX64-10054.dmg">Macintosh OSX用</a><br>  
<a href="https://secure.multix.jp/download/KanPan/JyouBan_Win32-10054.zip">Windows用</a>  
</td></tr>  
</table>

<hr>

<h4 id="">主な特徴</h4>

<ul>
<li>没入型フルスクリーン表示ができます。現状最大5Kディスプレイまで動作します。</li>
<li>ウィンドウを任意のサイズにリサイズしてプレイできます。大きくして見やすくすることも小さくして画面の隅に置いておくようなこともできます。</li>
<li>スクリーンショット（画面キャプチャ）をワンキーで取得・保存できます。</li>
<li>画面スワイプなどの誤操作によるブラウザバック事故が発生しません。</li>
<li>本来のDMMゲーム画面にはないDMMログアウト機能があります。</li>
<li>アプリケーション起動時にキャッシュを消すことも、終了時にCookieを消去することも容易にできます。</li>
<li>アプリケーション毎に独立したキャッシュ・Cookie保存領域を確保することでOS標準ブラウザにインストールされたアドウェアや悪意あるプログラムから干渉されにくくしています。</li>
<li>OS共通のネットワーク設定とは独立したプロキシ設定をアプリケーション毎に設定できます。</li>
<li>いわゆる専ブラによくある裏ステ表示機能等はありません。一般的Webブラウザよりもゲームプレイに特化した必要最小限の表示ができる単体アプリケーションです。</li>
</ul>

<hr>

<h4 id="">スクリーンキャプチャ</h4>

<p>艦これの場合のアプリケーション表示例です。他のゲームでも同様です。</p>

<p><img src="http://multix.jp/content/images/2015/03/2015-03-07-17-19-30.png" alt="艦板-KanPan-とそのバリアント"></p>

<p>等倍プレイサイズ（ゲーム画面800x480）・・・
<a href="http://multix.jp/content/images/2015/03/2015-02-01-15-11-37.png">ドロップ画面</a></p>

<p>縮小プレイ 60%（480x288）・・・
<a href="http://multix.jp/content/images/2015/03/2015-02-01-15-12-10.png">これでも普通に遊べます</a></p>

<p>拡大プレイ 160%（1280x768）・・・
<a href="http://multix.jp/content/images/2015/03/2015-02-01-15-11-52.png">この位がプレイしやすい</a></p>

<p>iMac/27in 3Kフルスクリーン 300%（2560x1440）・・・<a href="http://multix.jp/content/images/2015/03/2015-02-01-15-12-29.png">実寸表示推奨</a></p>

<p>5Kフルスクリーン 600%表示（5120x2880）・・・（自粛）</p>

<hr>

<h4 id="">動作環境</h4>

<p>少なくとも最新の<strong>Adobe Flash Player</strong>が動作し、DMMオンラインゲームの推奨動作環境に準じている必要があります。</p>

<ul>
<li>OSX 10.7(Lion)<sup id="fnref:2"><a href="http://multix.jp/dmm-playing-viewer/#fn:2" rel="footnote">2</a></sup> 以降がインストールされた<strong>64bitマルチコアCPU</strong>搭載の Machintosh コンピュータ（OSX Yosemiteで動作確認）</li>
<li>Windows 7以降で<strong>.NET Framework 4.x</strong><sup id="fnref:3"><a href="http://multix.jp/dmm-playing-viewer/#fn:3" rel="footnote">3</a></sup>がインストールされた32bitまたは64bitマルチコアCPU搭載の Windows コンピュータ<sup id="fnref:4"><a href="http://multix.jp/dmm-playing-viewer/#fn:4" rel="footnote">4</a></sup>（Windows8.1Updateで動作確認）</li>
</ul>

<hr>

<h4 id="adobeflashplayernpapi">別途 Adobe Flash Player(NPAPI)が必要</h4>

<p>プレイングビューワを使用するには別途<strong>Adobe Flash Player</strong>が必要です。必要な方は<a href="http://get.adobe.com/jp/flashplayer/otherversions/">こちら</a>から<strong>NPAPI</strong>版を選んでダウンロードし、インストールしてください。IE用やPPAPI版では動作しません。<sup id="fnref:5"><a href="http://multix.jp/dmm-playing-viewer/#fn:5" rel="footnote">5</a></sup></p>

<hr>

<h4 id="">インストールと起動方法</h4>

<p>インストーラはありません。ダウンロードしたZIPファイルを展開して適当なフォルダにコピーしてください。 <br>
初回起動時はOSのセキュリティ制限によってプログラムの起動がブロックがされる事がありますが、以下の方法で解除してください。</p>

<p><strong>Windowsの場合</strong></p>

<ul>
<li>実行ファイルを右クリックして<strong>ブロックの解除(K)</strong>ボタンを押してください。同梱のWinRapper.exeにも同様の操作が必要です。ただしこの操作にはPCの設定により管理者権限が必要になることもあります。
<img src="http://multix.jp/content/images/2015/03/2015-03-07-18-41-02.png" alt="艦板-KanPan-とそのバリアント"></li>
<li>この問題はWindows上でダウンロードし標準のZIPアーカイブ展開ツールを使用した場合に発生します。他のPCやMachintoshでダウンロードしてUSBメモリでコピーした場合や、7-Zip等のサードパーティー製展開プログラムを使用した場合には<strong>この問題は生じません</strong>。</li>
</ul>

<p><strong>Machintoshの場合</strong>
<img src="http://multix.jp/content/images/2015/03/2015-03-07-18-49-28.png" alt="艦板-KanPan-とそのバリアント"></p>

<ul>
<li>システム環境設定のセキュリティパネルで少なくとも<strong>ダウンロードしたアプリケーションの実行許可</strong>が禁止されていない必要があります。初回起動時に起動ブロックが掛かった場合はこの画面の<strong>このまま開く</strong>ボタンで実行許可を与えることができます。
<img src="http://multix.jp/content/images/2015/03/2015-03-07-18-52-27.png" alt="艦板-KanPan-とそのバリアント"></li>
<li>あるいは起動できなかったアプリを右クリックしてコンテキストメニューを出し<strong>optionキーを押しながら開く</strong>とブロック解除の確認画面を出すことができるのでここから起動を許可することができます。
<img src="http://multix.jp/content/images/2015/03/2015-03-07-18-49-41.png" alt="艦板-KanPan-とそのバリアント">
<img src="http://multix.jp/content/images/2015/03/2015-03-17-15-08-44.png" alt="艦板-KanPan-とそのバリアント">
<img src="http://multix.jp/content/images/2015/03/2015-03-07-18-49-50.png" alt="艦板-KanPan-とそのバリアント"></li>
<li>この問題は<strong>Macintosh上でダウンロード</strong>したアプリケーションの場合に発生します。Windowsや他のPCでダウンロードしてUSBメモリやNAS経由でコピーした場合には<strong>この問題は生じません</strong>。</li>
</ul>

<hr>

<h4 id="">つかいかた</h4>

<p>F1キーを押すか左上のメニューボタンをタップするとメニューを開くことができます。ほとんどの機能には簡単に呼び出せるショートカットキーが割り当てられています。 <br>
<img src="http://multix.jp/content/images/2015/03/2015-03-07-17-44-39.png" alt="艦板-KanPan-とそのバリアント"></p>

<ul>
<li><p><strong>最前面に固定</strong> command+T(OSX) Ctrl+T(Win) <br>
ウィンドウを他のウィンドウより常に前に表示できます。トグル操作でオン／オフします。</p></li>
<li><p><strong>全画面表示</strong> command+F(OSX) Ctrl+F(Win) <br>
トグル操作で没入型フルスクリーン表示をオン／オフします。なおフルスクリーン表示中は他のウィンドウリサイズ操作はできません。ディスプレイサイズが小さい場合やタブレットPCに適します。</p></li>
<li><p><strong>等倍表示</strong> command+O(OSX) Ctrl+O(Win) <br>
ウィンドウをそのゲームの等倍表示サイズに調整します。ウィンドウサイズはゲームにより異なります。</p></li>
<li><p><strong>半画面表示</strong> command+H(OSX) Ctrl+H(Win) <br>
ウィンドウをディスプレイサイズの半分の横幅になるように調整します。2K以上の大画面ディスプレイで他のアプリケーションやブラウザと並べてプレイするときに適します。</p></li>
<li><p><strong>拡大</strong> alt+右カーソルキー <strong>縮小</strong> alt+左カーソルキー <br>
10%刻みでウィンドウサイズを拡大・縮小します。</p></li>
<li><p><strong>画面キャプチャ</strong> TAB または command+S(OSX) Ctrl+S(Win) <br>
プレイ画面のスクリーンショットをキャプチャして予め指定されたフォルダにPNG画像として保存します。デフォルトではOS標準のピクチャフォルダが指定されています。 <br>
タブレットボタンモードがオンの場合は画面右上のキャプチャボタンを押しても同様にスクリーンショットを保存できます。 <br>
DMMログイン画面ではTABキーはキャプチャ機能ではなくフォームフォーカス移動キーになります。 <br>
画像サイズはウィンドウサイズに依存します。フルスクリーン表示中の画像保存には数秒かかる場合があります。撮影保存中はタブレットボタンは消えます。基本的に連写はできません。</p></li>
<li><p><strong>キャプチャ保存場所指定</strong> （メニューからのみ実行可） <br>
スクリーンショット画像を保存する場所を変更できます。変更内容は次回起動時にも反映されます。</p></li>
<li><p><strong>プロキシを使用する</strong> （メニューからのみ実行可） <br>
OSのネットワーク通信設定とは別に、任意のプロキシアドレス・ポートを設定することができます。VPNと併用して安全な通信経路を確保したい場合などに使います。設定内容は次回起動時から有効になるため、設定後はいちどアプリケーションを終了してください。</p></li>
<li><p><strong>タブレットボタン</strong> （メニューからのみ実行可） <br>
プレイ画面左上に半透明表示されるメニューボタンと、右上に半透明表示されるキャプチャボタンのオン／オフをトグル操作で表示・非表示することができます。これらのボタンはスクリーンショット画像には写りません。 <br>
<img src="http://multix.jp/content/images/2015/03/menu.png" alt="艦板-KanPan-とそのバリアント" title=""> <br>
<img src="http://multix.jp/content/images/2015/03/shot.png" alt="艦板-KanPan-とそのバリアント" title=""> <br>
ウィンドウのリサイズ状態や、タブレットPCのディスプレイアスペクトによってはタブレットボタン表示がゲームプレイのじゃまになる場合があります。その場合はこのメニュー操作で非表示とするようにしてください。ただし再度表示するにはF1キーでメニュー表示が呼び出せなければならないため、キーボードを接続していないタブレットPCでは注意が必要です。<sup id="fnref:6"><a href="http://multix.jp/dmm-playing-viewer/#fn:6" rel="footnote">6</a></sup></p></li>
<li><p><strong>再読込</strong> command+R(OSX) Ctrl+R(Win) <br>
いわゆるF5です。キャッシュとCookieは消去されません。DMMからログアウトはしません。</p></li>
<li><p><strong>ログアウト</strong><sup id="fnref:7"><a href="http://multix.jp/dmm-playing-viewer/#fn:7" rel="footnote">7</a></sup> （メニューからのみ実行可） <br>
ゲームログイン状態を終了してDMMログイン画面に遷移します。キャッシュとCookieは消去されません。</p></li>
<li><p><strong>Cookie消去して終了</strong> <br>
キャッシュとCookieを消去し、DMMからログアウトした状態でアプリケーションを終了します。</p></li>
<li><p><strong>終了</strong> command+Q(OSX) Alt+F4(Win) <br>
アプリケーションを終了します。ウィンドウを閉じるボタンを押すのと同じです。キャッシュとCookieは消去されません。DMMからログアウトはしません。</p></li>
<li><p><strong>公式コミュニティを表示</strong> （メニューからのみ実行可） <br>
別画面で公式コミュニティを表示します。OSのデフォルトブラウザを使います。DMMログイン状態は本アプリケーションとは別になります。</p></li>
<li><p><strong>build XXXXX</strong> <br>
本アプリケーションのリリースビルド番号表示です。選択するとこのサイトをOSのデフォルトブラウザで別画面表示します。</p></li>
<li><p><strong>開発ツール</strong> Alt+I（隠し機能） <br>
DOMエクスプローラ・Cookieブラウズ・デバッグコンソールを含むインスペクタウィンドウが別ウィンドウで起動します。ただしプロキシ設定が有効な場合は起動できません。またゲーム内通信を傍聴・干渉することもできません。</p></li>
</ul>

<hr>

<h4 id="">いわゆる専ブラじゃない・・・と思う</h4>

<p>ゲーム内API通信には一切干渉しません。従って裏ステ表示機能などもありません。そういう追加機能が必要な場合は、プロキシ設定と他の外部ツールを組み合わせて自己責任で環境構築・運用してください。</p>

<hr>

<h4 id="dmm">ゲーム内DMMポイント課金購入について</h4>

<p>アプリケーション内から通常通りDMMポイントを購入することができます。</p>

<p><img src="http://multix.jp/content/images/2015/03/2015-03-07-20-55-18.png" alt="艦板-KanPan-とそのバリアント"></p>

<p>ゲーム内で課金購入操作を行うとこのような画面になりますが<strong>ポイントを購入する</strong>ボタンを押すと別画面でポイントチャージ用のウィンドウが開きます。この時の別ウィンドウはOSデフォルトのブラウザ（IEやSafari）ではなく、本アプリケーションそのものが生成するサブウィンドウです。従ってこの際のキャッシュ・Cookie・操作履歴はOS側の通常のデータとしては残らず、本アプリケーションのワークエリア内にのみ保存されます。これらは後述の操作で消去・リセットすることができます。<sup id="fnref:8"><a href="http://multix.jp/dmm-playing-viewer/#fn:8" rel="footnote">8</a></sup></p>

<p>当然ですが、通信環境等にセキュリティ上の不安がある環境下での課金操作には熟慮が必要です。本アプリケーションでも悪意ある外部プログラムの侵入や高度なアドウェアの干渉をブロックしたり排除することを完全に予防することはできません。少しでも不安がある場合は何もすべきではありません。そもそも本アプリケーションを信用出来ない場合は使用するべきではありません。</p>

<hr>

<h4 id="">再起動後も維持される保存内容</h4>

<p>アプリケーションを終了・起動しなおしても以下の設定は（設定が消去されるまで）維持されます。</p>

<ul>
<li>キャプチャ保存場所</li>
<li>プロキシ設定</li>
<li>タブレットボタン表示／非表示</li>
<li>DMMログイン状態（DMMのCookie有効期間に依存）</li>
</ul>

<p>以下については維持されず再起動毎にリセットされます。</p>

<ul>
<li>最前面固定（常にオフ）</li>
<li>全画面状態（常にオフ）</li>
<li>ウィンドウサイズ（常にデフォルトサイズ）</li>
<li>ウィンドウ位置（常にディスプレイ中央）</li>
</ul>

<hr>

<h4 id="">設定クリア起動について</h4>

<p>アプリケーションを起動する時、以下の特別な方法を取ることができます。</p>

<ul>
<li><p><strong>Shiftキーを押しながら起動</strong>するとキャッシュを消去して起動できます。ウィンドウが表示されるまでキーは押したままにしてください。Cookieは削除されないのでDMMログイン状態は維持されます。ゲーム運営からキャッシュを消去して起動するように指示された場合・キャッシュが壊れていてゲームローディングが途中で止まる場合・サーバメンテナンス後の最初の起動時はこの操作を行ってください。</p></li>
<li><p><strong>CtrlキーとShiftキーを押しながら起動</strong>するとキャッシュとCookieと、すべての保存設定を完全に消去し、DMMログインもログオフされた状態で起動できます。ウィンドウが表示されるまでキーは押したままにしてください。キャプチャ保存場所やプロキシ設定はリセットされて初期状態に戻ります。通常の起動ではFlash画面がホワイトアウトして操作不能になるような場合に試してください。</p></li>
</ul>

<hr>

<h4 id="">アンインストール方法</h4>

<p>アプリケーションがOSの設定やレジストリを汚染することはありませんので何時でもアプリケーションを削除することができます。ただしキャッシュやCookieはOSのワークエリア内に保存されているため、ただアプリケーションを削除しただけではこれらが消えずに残ってしまいます。したがってアプリケーションを完全に終了したあと、ZIPパッケージに同梱の<code>deleteLocalData.sh</code>(OSX)または<code>deleteLocalData.bat</code>(Win)を必ず実行してからアプリケーションを削除してください。キャッシュ・Cookie・設定保存値をゴミ箱にも残さずに消去できます。消去内容（各ゲーム毎に異なります）はこのファイルをテキストエディタやメモ帳で閲覧することで確認できます。</p>

<hr>

<h4 id="">既知の不具合</h4>

<ul>
<li><p>Flashローディング途中にウィンドウリサイズをすると画面表示が乱れること（不要な黒帯が入る等）があります。Flashが完全に起動したあとでウィンドウリサイズし直すことで治ります。</p></li>
<li><p>極端かつ高速なリサイズ操作（特に縮小操作）を行うとFlashが勝手にリロードすることがあります。</p></li>
<li><p>DMMログイン画面以外の時にキャプチャ保存場所指定を行うとアプリケーションが異常終了する場合がまれにあります。ゲームログイン後〜Flash動作中にこれを行うことはなるべく避けるほうが良いでしょう。</p></li>
<li><p>全画面プレイ時等にキャプチャ操作をすると時間がかかることがありますが、仕様です。保存速度・連写可能な最短間隔はPCの性能に依存します。5Kディスプレイ全画面で快適に動かしたい場合16GB以上のメインメモリはある方が良いでしょう。</p></li>
<li><p>OSX版では全画面モードから通常表示に戻した時、ウィンドウタイトルが消える場合があります。実害はありませんが、これは本アプリケーションではなく使用しているライブラリにて既知のバグです。</p></li>
<li><p>ゲームによってはゲーム内音量設定等がキャッシュ消去を行ってもリセットされないことがありますが、これはそれぞれのゲームに依存した仕様です。<sup id="fnref:9"><a href="http://multix.jp/dmm-playing-viewer/#fn:9" rel="footnote">9</a></sup></p></li>
<li><p>OSX用の<a href="https://kancolle.sanaechan.net/">航海日誌</a>と組み合わせる場合、プロキシ設定を<code>http=localhost:8888</code>のように記述してください。これはHTTP通信のみをプロキシに通すという意味です。このように通信種別を制限しない場合、<strong>Network disabled</strong>となって接続できません。なお<a href="http://nekopanda.blog.jp/">航海日誌拡張版</a>については現在のところこのような問題はありません。（一般的な<code>localhost:8888</code>と普通に記述してよい）<sup id="fnref:10"><a href="http://multix.jp/dmm-playing-viewer/#fn:10" rel="footnote">10</a></sup></p></li>
</ul>

<hr>

<h4 id="usb">USBメモリ等からの起動について</h4>

<p>PCのローカルドライブにインストールせず、外部メディアからアプリケーションを起動する場合でも、デフォルト状態ではキャッシュ・Cookie・各種設定はPCのシステムワーク領域に保存されます。従ってアプリケーション終了後、外部メディアを抜く前にZIPパッケージに同梱の<code>deleteLocalData.sh</code>(OSX)または<code>deleteLocalData.bat</code>(Win)を実行してシステムワーク領域からデータを消去するようにしてください。<sup id="fnref:11"><a href="http://multix.jp/dmm-playing-viewer/#fn:11" rel="footnote">11</a></sup></p>

<hr>

<h4 id="">ソースファイルの入手</h4>

<p>Machintosh OSX版についてはZIPパッケージ内にすべてのソースファイル／リソースファイルが内包されています。Windows版についてはアプリケーション起動中に<code>C:¥Users¥&lt;ユーザ名&gt;¥AppData¥Local¥Temp</code>内にソースファイルフォルダが展開されていますので探してみましょう。<sup id="fnref:12"><a href="http://multix.jp/dmm-playing-viewer/#fn:12" rel="footnote">12</a></sup></p>

<hr>

<h4 id="">動作原理について</h4>

<p>通常のブラウザ内ゲームプレイ画面のCSSパラメータ<sup id="fnref:13"><a href="http://multix.jp/dmm-playing-viewer/#fn:13" rel="footnote">13</a></sup>を、ウィンドウリサイズ操作時に動的に書き換えることでウィンドウ枠いっぱいにゲーム画面が拡大し、またそれ例外の部分が黒く見えるようにしているだけです。同時に画面スワイプやBSキーによるブラウザバック、ドラッグ＆ドロップによる画面遷移を禁止している他は普通のWebブラウザです。DMMログイン画面とゲーム画面以外へは原則として移動も表示もできません。<sup id="fnref:14"><a href="http://multix.jp/dmm-playing-viewer/#fn:14" rel="footnote">14</a></sup></p>

<hr>

<h4 id="">開発環境・ライブラリ</h4>

<p>本アプリは以下を母体に構成されています。（2015/03/08時点）</p>

<ul>
<li>Nw.js(node-webkit) 0.12.3 -- アプリケーション全体</li>
<li>Chromium 41.0.2272.76 -- Webブラウザコア</li>
<li>v8 3.32.7 -- JavaScriptエンジン</li>
<li>Node.js 1.2.0 -- アプリケーションコア</li>
<li>openssl 1.0.1k -- SSL/TLS通信コンポーネント</li>
<li>zlib 1.2.5 -- 圧縮リソース展開コンポーネント</li>
<li>.NET Framework 4.0/4.5.2 -- Windowsキャッシュ削除機能</li>
</ul>

<p>各々の扱いはそれぞれの利用規約と領布ライセンスによります。</p>

<hr>

<h4 id="">更新履歴</h4>

<ul>
<li>build 10053 [2015/11/20] Update Nw.js v0.12.3, Flash 19.0.0.245 <br>
DMMフローティングバナーが画面上部に出てくる表示不具合に対応  </li>
<li>build 10053 [2015/05/25] Update Nw.js v0.12.2 <br>
既知の不具合に航海日誌（Mac）との接続不具合対処法を追記</li>
<li>build 10052 [2015/05/14] Update Flash 17.0.0.188対応</li>
<li>build 10051 [2015/04/28] Update Nw.js v0.12.1, Flash 17.0.0.169</li>
<li>build 10050 [2015/04/02] Fix JQuery</li>
<li>build 10049 [2015/03/30] Update Flash 17.0.0.149対応</li>
<li>build 10048 [2015/03/16] Update Flash 17.0.0.135対応 <br>
DMMシステムメンテナンス画面に追加対応  </li>
<li>build 10047 [2015/03/08] Update Nw.js v0.12.0</li>
<li>build 10046 [2015/03/03] 一般公開初版</li>
</ul>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>Linux用は提供していません。出来ないわけではなく、単にFlash Runtimeが簡単には動かないとかそもそも必要なlibが足りないとか、個別環境に依存する労力が大変面倒なためです。その説明だけで本一冊は書けるでしょう。実力のある人なら自分で改造・対応・試行錯誤されたほうが簡単です。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:1" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:2"><p>OSX/32bit用は提供していません。これは対応プラットフォームで32bit環境というのはかなり特殊ケースになるため用意する意味がほぼ無いからです。とはいえ同梱のNw.jsコンポーネントを32bit版に差し替えるだけで動きます。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:2" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:3"><p>.NET Runtimeが入ってなくても実はしれっと動いたりしますが、アプリ内からキャッシュやCookieがちゃんと消せなかったりします。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:3" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:4"><p>Windows/64bit用は提供していません。理由は多くのゲームが64bitネイティブなFlashに対応していないからです。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:4" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:5"><p>対応するFlash Playerがインストールされていない場合はDMMログイン後にこのFlash Payerダウンロードページに飛びます。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:5" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:6"><p>現状は各々画面左上と右上固定ですがバリアントによってはゲーム内操作と干渉します。この場合はウィンドウを上下または左右に広げて意図的に黒帯を出すようにすると改善します。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:6" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:7"><p>DMMオンラインゲームの画面にはログアウト機能が用意されていません。またブラウザを閉じただけではブラウザやゲームサーバ内部的には事実上ログインしたままになります。本アプリケーションでも明示的にログアウト操作をしない限り終了後もログインしたままになりますが、これはDMM側の基本仕様のため回避不可能です。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:7" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:8"><p>ようするに通信データを保存する場所がブラックボックスではなく、ユーザレベルで自己管理できるようになっています。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:8" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:9"><p>ゲームによってはF5連打監視や不正プレイ監視設定（発動すると一定時間ログインブロックされウィンドウがホワイトアウトしてしまう）も同時に消去/初期化されるようです。恒久的なものでなければCtrl+Shiftキーを押しながら起動することで解消できるはずです。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:9" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:10"><p>航海日誌拡張版はIPv4のみLISTENしますが、航海日誌はIPv4/v6両方ともLISTENするという違いがありますが、それ以上のことはわかっていません。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:10" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:11"><p>若干手を加えることでUSBメモリ内にワークエリアを設定・固定することもできます。OSX版では起動app内のlaunch.sh中の起動オプションに、Windows版ではショートカットを作成してその起動オプションに、--data-path=&lt;保存フォルダ>を追加して必ずそれから起動してください。ただしこうして設定した保存フォルダは用意されたキャッシュ消去操作方法の対象となりません。手動で削除するか、キャッシュ消去操作関係（WinRapper.iniやdeleteLocalData.*）も書きなおす必要があります。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:11" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:12"><p>アプリケーションが正常終了するときに展開コードは削除されます。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:12" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:13"><p>ゲームによってCSS書き換え内容に異なるため、ゲーム別にバリアントを用意しています。ただしこの対応箇所とアイコン以外は全ゲーム共通のコードです。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:13" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:14"><p>ただしDMM提供ページ内のセキュリティ監視関係やアナリシス外部通信等はブロックせずにそのままバックグラウンドで動かしています。これらをブロックするのはDMM利用規約やゲーム利用規約に反する可能性があります。 <a href="http://multix.jp/dmm-playing-viewer/#fnref:14" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[WindowsのヘルプファイルをHTML化する]]></title><description><![CDATA[<p>UWSC添付ヘルプが今時のWindowsでは普通には開けず不便極まりないので自分が使うために CHM Decoder でHTMLに変換してみた。内容には全く手を加えていない。無論文書著作権は先方にある。元はフリーソフトウェアの添付物ではあるがその点にはご留意いただきたい。</p>

<p><a href="http://multix.jp/uwsc.html" target="_blank">UWSCヘルプファイル(HTML版) v5.2.0 2015/10/18版 (Shift_JIS)</a></p>

<hr>

<h4 id="uwsc">UWSC</h4>

<p><a href="http://www.uwsc.info">UWSC公式サイト</a></p>

<blockquote>
  <p>あなたが行っている定型作業は<br>
  時間の無駄だとは思いませんか<br>
  退屈だとは感じませんか。</p>
</blockquote>

<ul>
<li>マウスとキーボード入力を記録して再生する事ができます。 （スクリプト形式ですので自由に編集する事ができます）</li>
<li>強力なスクリプト言語によりアプリの操作ができます。 （COMオブジェクト、DLLの利用も可能です）</li>
<li>スケジュール機能により指定時間や指定ウィンドウが現れた時などの指定ができます。</li>
</ul>

<p>以上、公式サイトからの引用。</p>

<p>コマンドプロンプトでのバッチ処理やPowerShell、CygwinからのCLI操作で事が済めばそれに越したことはないが、WindowsはやはりGUI主体のOSなので、CLI未対応アプリのほうが圧倒的多数だったりする。それでも定型作業のネタは尽きないし、どうにかしてGUI操作をバッチ処理できればルーチンワークのミス率が大幅に減るんじゃないかという懸念は常に付きまとう。UWSCはそんな時に役立つ強力なツールになりうる。</p>

<p>マウスやキーボード入力の操作過程をレコーディングして簡単に再生操作することもできるが、その操作内容を専用スクリプト言語で書き出せるので、これを編集加工したり、あるいはゼロから緻密にスクリプトをコーディングするほうが主だろう。</p>

<p>その専用スクリプト言語は、言語仕様がBASIC風である。例えばあるBMP画像とデスクトップ画面とを比較して見つかったか否かの真偽値と、見つかった場合の座標を得るにはこんなコードを書く。</p>

<pre><code class="language-brush:plain">// 画像チェック

If ChkBmp("検査画像") Then</code></pre>]]></description><link>http://multix.jp/windows-helpfile-to-html/</link><guid isPermaLink="false">18be7c21-2eab-4687-9901-f936d1e3c3c1</guid><category><![CDATA[ユーティリティ]]></category><category><![CDATA[Windows]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Tue, 17 Feb 2015 06:41:00 GMT</pubDate><content:encoded><![CDATA[<p>UWSC添付ヘルプが今時のWindowsでは普通には開けず不便極まりないので自分が使うために CHM Decoder でHTMLに変換してみた。内容には全く手を加えていない。無論文書著作権は先方にある。元はフリーソフトウェアの添付物ではあるがその点にはご留意いただきたい。</p>

<p><a href="http://multix.jp/uwsc.html" target="_blank">UWSCヘルプファイル(HTML版) v5.2.0 2015/10/18版 (Shift_JIS)</a></p>

<hr>

<h4 id="uwsc">UWSC</h4>

<p><a href="http://www.uwsc.info">UWSC公式サイト</a></p>

<blockquote>
  <p>あなたが行っている定型作業は<br>
  時間の無駄だとは思いませんか<br>
  退屈だとは感じませんか。</p>
</blockquote>

<ul>
<li>マウスとキーボード入力を記録して再生する事ができます。 （スクリプト形式ですので自由に編集する事ができます）</li>
<li>強力なスクリプト言語によりアプリの操作ができます。 （COMオブジェクト、DLLの利用も可能です）</li>
<li>スケジュール機能により指定時間や指定ウィンドウが現れた時などの指定ができます。</li>
</ul>

<p>以上、公式サイトからの引用。</p>

<p>コマンドプロンプトでのバッチ処理やPowerShell、CygwinからのCLI操作で事が済めばそれに越したことはないが、WindowsはやはりGUI主体のOSなので、CLI未対応アプリのほうが圧倒的多数だったりする。それでも定型作業のネタは尽きないし、どうにかしてGUI操作をバッチ処理できればルーチンワークのミス率が大幅に減るんじゃないかという懸念は常に付きまとう。UWSCはそんな時に役立つ強力なツールになりうる。</p>

<p>マウスやキーボード入力の操作過程をレコーディングして簡単に再生操作することもできるが、その操作内容を専用スクリプト言語で書き出せるので、これを編集加工したり、あるいはゼロから緻密にスクリプトをコーディングするほうが主だろう。</p>

<p>その専用スクリプト言語は、言語仕様がBASIC風である。例えばあるBMP画像とデスクトップ画面とを比較して見つかったか否かの真偽値と、見つかった場合の座標を得るにはこんなコードを書く。</p>

<pre><code class="language-brush:plain">// 画像チェック

If ChkBmp("検査画像") Then  
    // 成功すると特殊グローバル変数に取得した座標が入っている
    Print "X=" + G_IMG_X + " Y=" + G_IMG_Y
Else  
    Print "見つからなかった"
EndIf

Function ChkBmp (BmpName)  
    // Result はExitでコール元に返される特殊グローバル変数
    Result = Chkimg("Resources\" + BmpName + ".bmp")
    Exit
Fend  
</code></pre>

<p>この画像比較・座標取得という機能が万能すぎて目眩がする。「GUI OSの自動操作」というジャンルでは OSX 標準装備の Automator + Apple Script がよく知られるところだが、残念ながらこんな機能があるかまでは過分にして知らない。もしあったら多分みんなもっと積極的に使っていて一般化しているんじゃないかと思える。</p>

<p>そんな強力な UWSC の泣き所はやはりこの言語仕様だろう。組込関数も独特なものが多い。添付のヘルプファイルや世間に流通するサンプルコードは欠かせない資料だ。ところがこのヘルプファイルは古き良き .chm 形式である。これは Windows Vista 以降ではセキュリティ上の問題からもはや非推奨になっているフォーマットで、ヘルプビューワもOS標準からは取り除かれている。よって.chmファイルを開いて閲覧するには別途ダウンロードしてインストールするなり、Windows SDKを持ってくるなりしなければならない。それが1台だけならまだしも何台ものPCに入れなきゃならんとなると全くもってゲンナリしてくるし、まして普段使いの PCが Macなのだからそっちの広大なデスクトップでドキュメント参照しながらコーディングして実機でトライ＆デバッグするほうが生産的というものだ。</p>

<hr>

<h4 id="chmdecoder">CHM Decoder</h4>

<p><img src="http://multix.jp/content/images/2015/02/2015-02-18-16-15-52.png" alt=""></p>

<p><a href="http://gridinsoft.com/chm.php">http://gridinsoft.com/chm.php</a> <sup id="fnref:1"><a href="http://multix.jp/windows-helpfile-to-html/#fn:1" rel="footnote">1</a></sup></p>

<p>CHM Decoder は .chm ファイルをHTMLファイルセットに変換してくれる Windowsアプリである。使い方はごく簡単で、アプリを起動して .chm ファイルを読み込ませ、出力フォルダを指定して結果を書き出すだけだ。.chm ファイルの日本語文字コードは Shift_JIS であるが特に文字化けすることもなくそのまま書き出される。出力結果がどんな様子になるかは実際に UWSCヘルプファイルを変換した先のリンクを見て頂きたい。ヘルプビューワにあった検索機能や印刷機能は損なわれるがHTML化してブラウザ上で閲覧・検索できるメリットのほうがたいていは上回るので、それほどのデメリットでもないだろう。</p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>Local download <a href="https://secure.multix.jp/download/chmdecoder-2.2.zip">chmdecoder.exe ver2.2 (13.04.2014)</a> <a href="http://multix.jp/windows-helpfile-to-html/#fnref:1" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item></channel></rss>