takayukisの日記 このページをアンテナに追加 RSSフィード

2016.05.30(月)

[]Gitbucket + Jetty + SSL + Windowsサービス化

環境は以下の通り。少し前のメモなので古いです。

インストール

C:\GitBucketJetty

ここにJettyの配布ファイルを展開した中身を置きます。

demo-baseをコピーして必要なファイルのみ残します。

gitbucket-base
│  start.ini
│
├─etc
│      keystore
│
├─lib
│  └─ext
├─resources
├─start.d
│      https.ini
│      ssl.ini
│
└─webapps
       gitbucket.war
  • libとresourcesは、jettyを実行すると自動的に作成されます(作成したとき、jettyは自動的に終了する)。
  • keystoreは確認のために使用し、後で入れ替えます。

start.ini

コメントアウトした項目

jaas, rewrite, websocket, test-realm, jndi

その他も不要なモジュールがあるかもしれないが、とりあえずそのままにしている。

リポジトリの保存先を環境変数で指定

システム環境変数 GITBUCKET_HOME にリポジトリの保存先のパスを指定する。

C:\GitBucketJetty\gitrepos

サービスに登録

Apache commons daemonを使用してサービスに登録する。

commons-daemon-1.0.15-bin-windows.zipをダウンロードした。

アーカイブから次のファイルをコピーしてリネーム

/prunmgr.exe
/amd64/prunsrv.exe

C:\GitBucketJetty\GitBucketJettySrv.exe
C:\GitBucketJetty\prunmgr.exe
サービスインストール用バッチファイル

install_jetty_gitbucket.bat

set JETTY_HOME=C:\GitBucketJetty
GitBucketJettySrv.exe ^
//IS//GitBucketJettyService ^
--DisplayName="GitBucketJetty Service" ^
--Install=%JETTY_HOME%\GitBucketJettySrv.exe ^
--LogPath=%JETTY_HOME%\logs ^
--LogLevel=Debug ^
--StdOutput=auto ^
--StdError=auto ^
--StartMode=Java ^
--StopMode=Java ^
--Startup=auto ^
--StartPath=%JETTY_HOME% ^
--Classpath=%JETTY_HOME%\start.jar ^
--StartClass=org.eclipse.jetty.start.Main ^
--StopClass=org.eclipse.jetty.start.Main ^
++StopParams=--stop ^
++JvmOptions=-Xmx64m ^
++StartParams=jetty.home=%JETTY_HOME% ^
++StartParams=jetty.base=%JETTY_HOME%\gibucket-base ^
++JvmOptions=-DSTOP.PORT=8079 ^
++JvmOptions=-DSTOP.KEY=jetty ^
++StartParams=jetty.logs=%JETTY_HOME%\logs ^
++StartParams=org.mortbay.jetty.Request.maxFormContentSize=1000000

※メモリ関連のJVMオプションは適当

サービスアンインストール用バッチファイル
set JETTY_HOME=C:\GitBucketJetty
GitBucketJettySrv.exe //DS//GitBucketJettyService

ここまでできたらサービスを起動して、Webブラウザで以下のURLにアクセスし、正常起動を確認する。

https://localhost:8443/gitbucket/

確認ができたらサービスは終了しておく。

設定によっては終了もうまくいかない場合があるので、タスクマネージャを起動してプロセスが消えることを確認する。

SSL用のkeystoreの作成(オレオレ証明書

フォルダを1つ作って、その中で作業を行うことにする。

C:\GitBucketJetty\keys

gnuwin32がインストールされていればopensslコマンドがある。

OpenSSL for Windowsインストールしても良い。

openssl.cnf

いずれの場合も、openssl.cnfが存在しないため、エラーになるので存在させておく。

空のファイルでもエラーになるため、次のファイルを用意する。

後ほど、このファイルのパスを環境変数 OPENSSL_CONF に設定する。

※このopenssl.cnfは適当に検索して見つけた。

C:\GitBucketJetty\keys\openssl.cnf

#
# SSLeay example properties file.
# This is mostly being used for generation of certificate requests.
#

RANDFILE        = .rnd

####################################################################
[ ca ]
default_ca    = CA_default        # The default ca section

####################################################################
[ CA_default ]

dir        = C:\\openssl\\\bin\\demoCA    # Where everything is kept
certs        = $dir\\certs                # Where the issued certs are kept
crl_dir    = $dir\\crl                # Where the issued crl are kept
database    = $dir\\index.txt            # database index file.
new_certs_dir    = $dir\\newcerts            # default place for new certs.

certificate    = $dir\\cacert.pem                # The CA certificate
serial        = $dir\\serial                # The current serial number
crl        = $dir\\crl.pem                # The current CRL
private_key    = $dir\\private\\cakey.pem       # The private key
RANDFILE    = $dir\\private\\private.rnd     # private random number file

x509_extensions    = x509v3_extensions    # The extentions to add to the cert
default_days        = 365            # how long to certify for
default_crl_days    = 30            # how long before next CRL
default_md        = md5            # which md to use.
preserve        = no            # keep passed DN ordering

# A few difference way of specifying how similar the request should look
# For type CA, the listed attributes must be the same, and the optional
# and supplied fields are just that :-)
policy        = policy_match

# For the CA policy
[ policy_match ]
countryName            = match
stateOrProvinceName        = match
organizationName        = match
organizationalUnitName    = optional
commonName            = supplied
emailAddress            = optional

# For the ’anything’ policy
# At this point in time, you must list all acceptable ’object’
# types.
[ policy_anything ]
countryName        = optional
stateOrProvinceName    = optional
localityName        = optional
organizationName    = optional
organizationalUnitName    = optional
commonName            = supplied
emailAddress            = optional

####################################################################
[ req ]
default_bits        = 1024
default_keyfile     = privkey.pem
distinguished_name    = req_distinguished_name
attributes        = req_attributes

[ req_distinguished_name ]
countryName            = Country Name (2 letter code)
countryName_min        = 2
countryName_max        = 2

stateOrProvinceName        = State or Province Name (full name)

localityName            = Locality Name (eg, city)

0.organizationName        = Organization Name (eg, company)

organizationalUnitName    = Organizational Unit Name (eg, section)

commonName            = Common Name (eg, your website’s domain name)
commonName_max        = 64

emailAddress            = Email Address
emailAddress_max        = 40

[ req_attributes ]
challengePassword        = A challenge password
challengePassword_min    = 4
challengePassword_max    = 20

[ x509v3_extensions ]
ランダムファイル

パスを指定する必要がある。

後ほど、環境変数 RANDFILE に設定する。

キーの生成

次のバッチファイルを実行し、実行中に聞かれるパスワードなどに答えていく。

入力を間違った場合は、再実行すればよい。

最後にJettyのバージョンに依存したファイル名が登場するので、バージョンが異なる場合は適宜修正すること。

環境変数 JAVA_HOME も使用しているので、事前に設定しておくこと。

テスト用なので、10年間有効な証明書を生成している。

C:\GitBucketJetty\keys\genkey.bat

set BASEPATH=C:\GitBucketJetty\keys
cd %BASEPATH%
set OPENSSL_CONF=%BASEPATH%\openssl.cnf
set RANDFILE=%BASEPATH%\.rnd

rm jetty.pkcs12
rm keystore
rm server.crt
rm server.key

rem http://www.eclipse.org/jetty/documentation/current/configuring-ssl.html
rem Generating Keys and Certificates with OpenSSL
openssl genrsa -aes256 -out server.key 2048
openssl req -new -x509 -days 3650 -key server.key -out server.crt

rem Loading Keys and Certificates via PKCS12
openssl pkcs12 -inkey server.key -in server.crt -export -out jetty.pkcs12
"%JAVA_HOME%\bin\keytool.exe" -importkeystore -srckeystore jetty.pkcs12 -srcstoretype PKCS12 -destkeystore keystore

rem Generating obfuscated password
set /P PASSWORD="Enter password for keystore:"
java -cp ..\lib\jetty-util-9.3.7.v20160115.jar org.eclipse.jetty.util.security.Password %PASSWORD%

「Enter password for keystore:」では、「出力先キーストアのパスワードを入力してください:」で

入力したパスワードと同じものを指定する。

最後にkeystoreのパスワードを難読化して表示する。

OBF:で始まる文字列は後で使用する。

keystoreのコピー

このファイルを上書きする。

C:\GitBucketJetty\gibucket-base\etc\keystore

keystoreのパスワードを設定ファイルに記述

C:\GitBucketJetty\gibucket-base\start.d\ssl.ini

jetty.sslContext.keyStorePassword=OBF:1v2j1uum1xtv1zej1zer1xtn1uvk1v1v
jetty.sslContext.keyManagerPassword=OBF:1v2j1uum1xtv1zej1zer1xtn1uvk1v1v
jetty.sslContext.trustStorePassword=OBF:1v2j1uum1xtv1zej1zer1xtn1uvk1v1v

Webブラウザでの確認

サービスを起動して正常起動を確認する。

Webブラウザで証明書の情報を確認する。

ファイアウォールの設定を行いアクセス可能にする

する。

rootパスワード変更とユーザー作成

Gitbucketのユーザー作成。

テスト用リポジトリの作成

作る。

GitSSLの検証を行わないように設定する

C:\Program Files (x86)\Git\etc\gitconfig

管理者権限でエディタを起動して sslVerify = false の行を追加する。

[http]
	sslCAinfo = /bin/curl-ca-bundle.crt
	sslVerify = false

※最新のgit 2.7.2にはgitconfigファイルが無かったが、Eclipseの Team -> Git -> Configurationで、

キー: http.sslVerify
値: false

を設定することでSSLの検証を回避できた。

ユーザーごとの設定は以下にある模様。

C:\Users\nobody\.gitconfig

git cloneして確認

確認する。

2016.05.23(月)

[]MySQLで日本語の文字が入ったレコードが検索できない時 (JDBC)

JDBC URLに次のプロパティを追加する。

jdbc:mysql://address:3306/dbname?useUnicode=true&characterEncoding=UTF-8

XMLに書くときは、 & を & に。

ツールにはしっかり設定していたのに、サーバの設定ファイルに付け忘れてた。意味不明の結果にしばらく悩んだ(恥)。

[]関連レコード件数で一括update (MySQL)

テーブルAとテーブルBが1対多の関係にあって、最適化か何かのためにAにBの件数を保持するような列があるとすると、以下のような書き方で一括設定できる。

UPDATE
    A, (SELECT count(*) as C, id FROM B) B2
SET
    A.count_cache = B2.C
WHERE
    A.id = B2.id

こんな書き方できるのかっていう。

2016.05.18(水)

[]子とすべての先祖の組み合わせを列挙

下のような複数の親と複数の子を持つことができる構造のデータで、子とすべての先祖の組み合わせを列挙してみます。

A→       →D
    B →C
E→       →F

子→親 とします。

public class Pair {
	public String key;
	public String val;
	public Pair(String key, String val) {
		this.key = key;
		this.val = val;
	}
}
public class FlowNetNode {
	public String name;
	public List<FlowNetNode> parents = new ArrayList<>();
	public FlowNetNode(String name) {
		this.name = name;
	}
	public boolean isAncestorName(String name) {
		for (FlowNetNode node : parents) {
			if (node.name.equals(name)) {
				return true;
			}
			if (node.isAncestorName(name)) {
				return true;
			}
		}
		return false;
	}
	public void addPairs(String key, List<Pair> pairs) {
		pairs.add(new Pair(key, name));
		for (FlowNetNode node : parents) {
			node.addPairs(key, pairs);
		}
	}
}
public class FlowNet {
	public Map<String, FlowNetNode> nodeMap = new HashMap<>();
	public void addFlow(String... nameList) {
		List<String> list = Arrays.asList(nameList);
		addFlow(list);
	}
	public void addFlow(List<String> nameList) {
		FlowNetNode parent = null;
		for (int i = nameList.size() - 1; i >= 0; i--) {
			String name = nameList.get(i);
			FlowNetNode node = nodeMap.get(name);
			if (node == null) {
				node = new FlowNetNode(name);
				nodeMap.put(name, node);
			}
			if (parent != null) {
				if (parent.isAncestorName(name)) {
					throw new RuntimeException("Duplication:" + name);
				}
				node.parents.add(parent);
			}
			parent = node;
		}
	}
	public List<Pair> makePair() {
		List<Pair> result = new ArrayList<>();
		for (Entry<String, FlowNetNode> entry : nodeMap.entrySet()) {
			FlowNetNode node = entry.getValue();
			node.addPairs(node.name, result);
		}
		return result;
	}
	public static void main(String[] args) {
		FlowNet net = new FlowNet();
		net.addFlow("A", "B", "C", "D");
		net.addFlow("B", "E");
		net.addFlow("G", "H", "I", "J");
		net.addFlow("H", "K");
		net.addFlow("L", "H");
		List<Pair> pairs = net.makePair();
		for (Pair pair : pairs) {
			System.out.println(pair.key + "=" + pair.val);
		}
	}
}

2016.04.27(水)

[]Gradleの謎文法(2) タスクの定義

Gradleにおいて、タスクは次のように定義します。

task hello {
    println 'Hello'
}

hello って識別子の宣言っぽいですよね。じゃあ task はメソッド hello の戻り値の型なんですか?

でもGroovyってトップレベルにメソッドを定義できるんでしたっけ?

もう一つ見てみましょう。フォルダをコピーするタスクです。

task myCopy(type: Copy) {
    from 'src'
    into 'dst'
}

どう見てもメソッドみたいな形をしていますが、そこにはメソッドは書けないんです。Groovy的には。

これはめちゃめちゃ混乱させられたんですが、実はGradleは、素のGroovyではなく、Groovyの文法をAST変換を使用して拡張しています。GradleはGroovyの上位互換言語だったんですね。


正解

task は暗黙の project のメソッドで、上記の hello や myCopy は引数の一つになります。

type: Copy は Map 型の引数、その後ろの中括弧 {...} は Closure 型の引数です。

projectの実装:

/src/core/org/gradle/api/internal/project/AbstractProject.java

public Task task(Map options, String task, Closure configureClosure)

public Task task(Map options, Object task, Closure configureClosure)

taskメソッド引数の順番が違うのですが、たぶんこれで合っていると思います。

タスク定義構文のAST変換の実装:

/src/core/org/gradle/groovy/scripts/internal/TaskDefinitionScriptTransformer.java

AST変換の実装を見ても、引数の順番が違う件についてすぐに理解できなかったのでスルーで。


Gradleについての所感

AST変換を行っている箇所は、ドキュメントでは当然の構文として登場するのでGroovy的に理解しようとするとはまります。タスク定義だけでなく、他にもあるらしいです。存在するのか知りませんがGroovyエディタが使えないし、軟弱者にはつらいです。AST変換までしてDSLを充実させる利点はあるんでしょうか。

Antの職人的スクリプト批判してGradleを持ち上げる文章を度々目にしたのですが、Gradleは完全なプログラミング言語ですし、ツールによる支援も今のところ無いので、もっと酷い事になり得るのではないかと思います。

あと、実行速度が遅い。

とは言え、Gradleはこれからのもはや主流。こっちに移行していこうと思っています。

2016.04.21(木)

[]Gradleの謎文法 tasks.withType(JavaCompile) {options.encoding = 'UTF-8'}について

JavaソースコードエンコーディングUTF-8としてコンパイルさせる設定は以下のように書ける。

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

変数 tasks のメソッド withType を呼び出しているが、その後ろに Closure と思われるブロックが続いている。GroovyのドキュメントのLanguage Specificationには、こんな書き方出てこなかったような。

変数 tasks は TaskContainer であるとDSLドキュメントにあるので参照してみる。

withTypeは3種類が継承されており、 Closure を引数に持つメソッドは DomainObjectCollection から継承されている。

<S extends T> DomainObjectCollection<S> withType(Class<S> type,
                                               Closure configureClosure)

DomainObjectCollection (Gradle API 2.12) withType(java.lang.Class,%20groovy.lang.Closure)

おそらく、呼び出されているメソッドはこれ。

試しに書き方を変えて同じ結果が得られるかを確認してみる。

tasks.withType(JavaCompile){println '(1)it=' + it}

tasks.withType JavaCompile, {println '(2)it=' + it}

// エラー
//tasks.withType (JavaCompile), {println '(3)it=' + it}

tasks.withType (JavaCompile)
{println '(4)it=' + it}

tasks.withType JavaCompile, {
    println '(5)it=' + it
}
tasks.withType(JavaCompile, {
    println '(6)it=' + it
})
tasks.withType (JavaCompile) {println '(7)it=' + it}

// おまけ
tasks.withType(JavaCompile).each({println '(8)it=' + it})
tasks.withType(JavaCompile).each {println '(9)it=' + it}
def xx = tasks.withType(JavaCompile)
xx.each({println '(10)it=' + it})
// xx {println 'it=' + it}

メソッド名を省略する文法は無いはずなので、意味合いが異なるが、おまけのeachを使っている書き方も含め、実行結果は同じになった。

件のコードを改めて見てみる。

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

ぱっと見、Groovyのドキュメントには記述を見つけることはできなかったが、末尾のClosureは引数の括弧に含める必要が無いとか。

試しに以下のコードを実行してみる。

class Hoge {
	void m1(String a, Closure cl) {
		cl.call()
	}
	void m2(String a, String b, Closure cl) {
		cl.call()
	}
	void m3(Closure cl1, Closure cl2) {
		cl1.call()
		cl2.call()
	}
	void m4(String a, Closure cl1, Closure cl2) {
		cl1.call()
		cl2.call()
	}
}

def hoge = new Hoge()
hoge.m1('a') {println 'm1.'}
hoge.m2('a', 'b') {println 'm2.'}
hoge.m3 {println 'm3-1.'} {println 'm3-2.'}
hoge.m3() {println 'm3-3.'} {println 'm3-4.'}
hoge.m4('a') {println 'm4-1.'} {println 'm4-2.'}

実行結果

m1.
m2.
m3-1.
m3-2.
m3-3.
m3-4.
m4-1.
m4-2.

シンタックスエラーにはならず、予想通りに動くので、ドキュメント化されていない仕様だったりするのかな。