systemd-nspawnで/sys/fs/selinuxがマウントできないので警告される件は無視することにした
ただのメモです。
CentOS7でsystemd-nspawn
の動作を検証してるなか、SELinuxを無効にした状態でコンテナを起動させると/sys/fs/selinux
がマウントできないと警告がでてきました。
# systemd-nspawn -D /var/tmp/mycontainer Spawning container mycontainer on /var/tmp/mycontainer. Press ^] three times within 1s to kill container. Failed to create directory /var/tmp/mycontainer//sys/fs/selinux: No such file or directory Failed to create directory /var/tmp/mycontainer//sys/fs/selinux: No such file or directory -bash-4.2#
/sys/fs/selinux
はSELinuxを無効にすると存在しないディレクトリ。調べたらsystemd-nspawnはHAVE_SELINUX
マクロでこのディレクトリのマウントを制御しています
systemd/nspawn.c at v219 · systemd/systemd · GitHub
そしてログを出力しているところはココ
systemd/nspawn.c at v219 · systemd/systemd · GitHub
ソースを見る限りその後の処理は続行しているし、問題なさそうかな〜と考えていたら、
で、同様の問題の当たっていたヒトがこの警告を消すためにログレベルをLOG_DEBUG
からLOG_WARNING
にさげるPRを送っていてmasterにマージされていました。なので、今後CentOS(systemd)のバージョンが上がれば、この警告は消えるので、現状無視することにした、というお話でした。
CentOS7でサーバを構築する際におこなっている基本設定
以前書いた
CentOS6でサーバを構築する際におこなっている基本設定 - オープンソースこねこね
のCentOS7バージョンです。調査中の内容もあるので、情報は随時更新する予定です。
※ここではサーバのロールにかかわらず行う基本的な作業をまとめています。通常この環境の上にWebサーバ(httpd, nginxなど)やDB(MySQLなど)をインストールすることになります。
※本文中のコマンドは基本的にrootユーザで行っているものとします。一般ユーザを使い必要に応じて都度root権限を使うようにしている方はsudo
をつけるなど、コマンドを読み替えてください。
- デフォルトターゲットの確認
- サービスの起動設定の確認
- ディクスサイズの確認
- ロケールの設定
- タイムゾーンとNTPの設定
- ネットワーク関連の設定
- yumリポジトリ
- bash-completion
- ファイアウォール (firewalld)
- sshd
- ログの管理
- logrotate
- sysctl
デフォルトターゲットの確認
systemctl get-default
で取得。CentoOS6までのデフォルトランレベルに相当します。
# systemctl get-default multi-user.target
multi-user.target
になっていることを確認します。
サービスの起動設定の確認
サービスの管理はsystemdによる管理に刷新されています。
systemctl list-unit-files
でサービスの一覧と状態を取得します。
# systemctl list-unit-files --type service --no-pager UNIT FILE STATE arp-ethers.service disabled auditd.service enabled auth-rpcgss-module.service static autovt@.service disabled blk-availability.service disabled brandbot.service static chrony-dnssrv@.service static
STATEの意味は以下の通り。
不要と思われるサービスはサーバリソースの無駄使いになるので、以下のコマンドで停止設定にしておきます。
# systemctl disable nginx.service # systemctl stop nginx.service
OSの起動時に実行させたいサービスは
# systemctl enable nginx.service # systemctl start nginx.service
としておきます。
ディクスサイズの確認
# df -h
サーバスペックにあったディクスサイズになっていることや、パーティションを確認します。
ロケールの設定
現在のロケールの設定を確認します。
# localectl status
利用可能なロケールをlocalectl list-locales
を調べ、
# localectl list-locales --no-pager
localectl set-locale
で変更。
# localectl set-locale LANG=ja_JP.utf8
ロケールを日本語にしたとき、manページを日本語にするために
# yum install man-pages-ja
をインストールします。
タイムゾーンとNTPの設定
現在のタイムゾーンを確認します
# timedatectl status
利用可能なタイムゾーンをtimedatectl list-timezones
で調べます
# timedatectl list-timezones --no-pager
timedatectl set-timezone
で変更。
# timedatectl set-timezone Asia/Tokyo
RTC in local TZ: yes
の設定になっていると警告がでるので以下を設定する
# timedatectl set-local-rtc 0
CentOS7では時刻同期サービスが2つあって従来のntpd
と新しいchronyd
がある。最小構成でOSインストールした場合は
chronyd
だけがインストールされている状態になっているはず。それぞれ設定ファイルは
/etc/chrony.conf
または/etc/ntp.conf
です。設定ファイルのフォーマットはほとんど同じなので
server 0.centos.pool.ntp.org iburst
のような、時刻問い合わせ先サーバの設定を確認します。
# systemctl start ntpd.service or # systemctl start chronyd.service
でサービスを起動します。
最後にtimedatectl
でntpによる時刻合わせを有効化します。
# timedatectl set-ntp yes
ネットワーク関連の設定
CentOS7ではネットワーク周りの管理がNetworkManagerを使った方式に変更されています。
NetworkManagerのコマンドラインインターフェースであるnmcli
を使って設定するのが標準の方法ですが、
ひとまず従来CentOS6の方法でできることは、それを踏襲してます(非推奨の方法だと思うので、のちのち更新予定です)。
まず、従来のifconfigなどがデフォルトでは提供されていないのでnet-tools
パッケージをインストールします。
# yum install net-tools
ホスト名
従来/etc/sysconfig/network
に記載していたホスト名はCentOS7では/etc/hostname
に記述します。
ネットワーク・インターフェースの確認
CentOS6のときと同様に/etc/sysconfig/network-scripts
配下のifcfg-xxx
を確認します。
設定を変更したら、
systemctl restart network
で反映させます。
IPアドレスの確認
# ifconfig
IPアドレスの設定が、設定通りか確認します。
ネットワークの疎通確認
# ping yahoo.co.jp
pingが外に向けて通るかを確認します。0% packet lossとなればOK。
# traceroute yahoo.co.jp
外につなぎに行く際にどのようなIPを経由するかを確認。ネットワーク管理者ではないので、ざっと結果を眺めるだけです。
yumリポジトリ
CentOS6のときと同様。epelとremiは(個人的に)必須なのでインストールします。epelは直接yumからインストールできます。
# yum install epel-release # yum install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
その他、設定ファイルなどはCentOS6のときと同様。
bash-completion
CentOS6のときと同様。素のbashのままだとzshになれた補完脳にはツライので、bash-completionを入れます。
# yum install bash-completion
インストールするだけで、ある程度補完が効くようになります。カスタマイズは特にしていません。
ファイアウォール (firewalld)
ファイアウォールはiptablesからfirewalldに変わりました。 firewalldはiptablesをバックエンドにしたファイアウォール管理のフロントエンド的なもので、実は内部的にはiptablesが依然使われています。とはいえ、設定に関してはやることがガラっと変わっています。
設定のための覚書
firewalldにはゾーンという概念があり、サービスやポートへの接続許可、拒否などの設定をまとめた単位。ゾーンはデフォルトで
- block
- dmz
- drop
- external
- home
- internal
- public
- trusted
- work
があります。ファイアウォールはこのゾーンをNICに割り当てることで、有効化され、機能します。
デフォルトではpublic
ゾーンが、有効化、割り当てされています。
# firewall-cmd --list-all
で確認できます。ゾーンについては
# firewall-cmd --list-all-zone
で一覧がでます。
デフォルトで有効になっているpublic
ゾーンは、明示的に許可した通信しか受け入れないようになっているので、基本的にこのゾーンに接続許可するポートやサービスの設定をしておけば、普通のファイアウォールの用途としては要件を満たせそうです。設定はfirewall-cmd
を実行することで行います。
設定
いくつか典型的な設定パターンをメモしておきます。
特定の接続先(この例では192.168.0.1/24)からのみサービスへの接続(この例ではSSH)を許可する場合:
# firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.0.1/24" service name="ssh" accept' --permanent
サービスの定義は/usr/lib/firewalld/services/
配下にあるxmlファイルを読む。ポート番号などがここに定義されています。
特定の接続先(この例では192.168.0.1/24)からのみポート8000の接続を許可する場合:
# firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.0.1/24" port port="8000" protocol="tcp" accept' --permanent
ポート80の接続を許可する場合:
# firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" port port="80" protocol="tcp" accept' --permanent
ちなみに上記のポートを単純に開放するだけなら
# firewall-cmd --zone=public --add-port=80/tcp --permanent
でも良さそう。個人的に設定の書き方を統一しておきたいので基本的に--add-rich-rule
を使っています。
設定を変更したら
# firewall-cmd --reload
で反映させます。その後確認のために
# firewall-cmd --list-all
でゾーンの設定を表示し、内容をチェックします。
sshd
CentOS6のときと同様。ポート番号を変えて、鍵認証オンリーにします。以下、編集および確認箇所のみを記載します。
/etc/ssh/sshd_config
# 22以外に変えます Port 2222 # rootログインは禁止 PermitRootLogin no # パスワード認証は禁止 PasswordAuthentication no
再起動して設定を反映します。
# systemctl restart sshd
ログの管理
syslogに代わる新しいログデーモンとしてjournaldが提供されています。systemdのサービスとして起動されたプログラムの標準出力、標準エラー出力は自動的にjournaldへと送られてログ管理の対象になります。ログはバイナリデータで保存され、専用のjournalctl
コマンドを使って検索、表示させるという操作になります。
ログの保存場所はデフォルトで/var/run/log/journal
。これは一時領域なので再起動したら消えてしまうため、/var/log/journal
に変更する。
# mkdir /var/log/journal # systemctl restart systemd-journald
上記のように/var/log/journal
を作成して、journaldを再起動すればOK。
ログの容量の最大サイズはファイルシステムの10%まで。変更するには/etc/systemd/journald.conf
ないで
SystemMaxUse=500M RuntimeMaxUse=500M
のようにサイズ指定すればよいです。
参考:http://www.server-memo.net/centos-settings/journald/journald.html
logrotate
ログのローテート設定です。journaldにではなく、サービスが直接ファイルになどに出力したログファイルをローテートします。
CentOS6のときと同様です。ログが無尽蔵に大きくなってディクスサイズを圧迫しないように設定、確認します。 初期設定時の他にhttpdなどのデーモンやログを出力するアプリケーションを追加した時にも目を通します。
/etc/logrotate.d/以下にログの対象ごとに設定ファイルがあるのでそれを確認します。例えばnginxは以下のようになっていたりします。
/etc/logrotate.d/nginx
/var/log/nginx/*log { create 0644 nginx nginx daily rotate 10 missingok notifempty compress sharedscripts postrotate /bin/kill -USR1 `cat /run/nginx.pid 2>/dev/null` 2>/dev/null || true endscript }
実際の設定はログのサイズや保存期間の要件などによって異なるので、それを考慮し設定します。
sysctl
CentOS6と同様、カーネルパラメータの設定です。CentOS6のときは/etc/sysctl.conf
を編集してましたが/etc/sysctl.d/
配下にxxx..conf
ファイルを配置しても設定できるので、そうしてます。(実はCentOS6のときからできたっぽいですが。参考:http://qiita.com/ngyuki/items/002a4a153ac6d8075bdb)
/etc/sysctl.d/override.conf
net.nf_conntrack_max = 1000000 net.netfilter.nf_conntrack_max = 1000000 net.core.somaxconn = 1024 vm.overcommit_memory = 1
設定の内容はCentOS6でサーバを構築する際におこなっている基本設定 - オープンソースこねこねを参照。設定値の反映は以下のコマンドを実行します。
# sysctl --system
参考: http://tsunokawa.hatenablog.com/entry/2015/09/02/175619
以下のコマンドで確認。
# sysctl -a
Pythonの開発環境
MacにPythonの開発環境を構築したかったので少しばかり調査、試行錯誤しました。調査メモと現時点で落とし所になった構成のまとめ。
調査
モダンなpython開発環境は
python
: ランタイム本体pip
: パッケージの管理コマンド(rubyのgem的な役割)virtualenv
: パッケージをディレクトリ単位で管理する仮想環境(rubyのbundler的な役割)pyenv
: 複数バージョンのpythonを管理(rbenvとかphpenvとかと同様)pyenv-virtualenv
: pyenvでvirtualenvの仮想環境を作成、管理するプラグイン
などが使われる模様。情報ソースは以下のサイトなどなど。
- [Python]pyenvとvirtualenvとpyenv-virtualenv - dackdive's blog
- 食わず嫌いしてたpyenvを使ってみたら便利過ぎて腰を抜かした話 - Qiita
- pyenv-virtualenvでディレクトリ単位のpython環境構築 - Qiita
- Pythonの仮想環境を構築できるvirtualenvを使ってみる - Qiita
以下のオペレーションを実際にやってみました。
所感。
- 複数バージョンのpythonは今のところ私には必要ない。pyenvはなくていい。
- 現状、やりたいことは、開発するときにプロジェクトディレクトリ配下にリソースをまとめたい。グローバルを汚染したくない、ということ。
virtualenv
はディレクトリに入ってからsource bin/activate
しないと有効にならない。また出るときはdeactivate
が必要。地味にめんどい。pyenv-virtualenv
でpyenv管理の仮想環境を作ってpyenv local
でローカルの設定をしておけば、ディレクトリ移動で仮想環境が有効になるが、仮想環境自体が~/.pyenv
配下に保存されて、インストールパッケージがプロジェクトのディレクトリ配下に配置されない。
いまひとつ不満が解消されないまま、さらに調べていたらいつも使っているdirenv
が組み込みの関数でpythonのvirtualenvを構築、ロードに対応していることを発見。
https://github.com/direnv/direnv/blob/c2cfad5ee664fed68224c44fd0549e73615f6b79/stdlib.sh#L352
試してみたらいい感じだったので、現状これとhomebrewでインストールできるpythonを使う構成に落ち着きました。以下、その環境の詳細となります。
環境構築
最終的な構成は以下になります。
- python (version2.x)
- pip
- virtualenv
- direnv
インストール手順。まずはpythonをインストール。Mac標準のよりバージョンが新しくpipもバンドルされてるのでhomebrewで提供されているものを使います。direnv
もhomebrewでインストールする。
$ brew install python $ brew install direnv
virtualenv
はpipでシステムにインストール。
$ pip install virtualenv
システムにインストールするものは以上です。
開発環境の使い方
プロジェクトディレクトリの作成
何か開発したいときはプロジェクトディレクトリを作成して、
$ mkdir python_project $ cd python_project
layout_python
を記述した.envrc
ファイルを用意する。
$ echo "layout_python" > .envrc && direnv allow
すると
direnv: loading .envrc Running virtualenv with interpreter /usr/local/bin/python New python executable in /path/to/python_project/.direnv/python-2.7.12/bin/python2.7 Also creating executable in /path/to/python_project/.direnv/python-2.7.12/bin/python Installing setuptools, pip, wheel...done. direnv: export +VIRTUAL_ENV ~PATH
となってpython_project/.direnv
にvirtualenvの仮想環境が構築された上に、VIRTUAL_ENV
環境変数が設定され仮想環境が有効になります。
(source bin/activate
したのと同様の状態になる)
パッケージのインストール
試しにpipでこの仮想環境にインストール済みのパッケージを確認してみます。
$ pip list pip (8.1.2) setuptools (27.2.0) wheel (0.30.0a0)
そこに新しくパッケージをインストールします。
$ pip install supervisor
もう一度pip list
すると
$ pip list meld3 (1.0.2) pip (8.1.2) setuptools (27.2.0) supervisor (3.3.1) wheel (0.30.0a0)
supervisor
がインストールできていることが確認できます。そしてこのプロジェクトディレクトリから出て、グローバルでのパッケージを確認すると
$ cd .. $ pip list mercurial (3.9.1) pip (8.1.2) setuptools (23.1.0) vboxapi (1.0) virtualenv (15.0.3) wheel (0.29.0)
プロジェクトでインストールしたものが入っていないことが確認にできます。再びプロジェクトディレクトリに入ると、仮想環境が有効になり、インストール済みパッケージももとに戻ります。
パッケージの書き出しと復元
pip freeze
でプロジェクトにインストールしたパッケージを書き出しておき、
$ pip freeze > requirements.txt
.direnv
を削除して、仮想環境を消去し、一度プロジェクトディレクトリを出ます。この状態でもう一度プロジェクトディレクトリに入ると
$ rm -rf .direnv $ cd .. $ cd python_project direnv: loading .envrc Running virtualenv with interpreter /usr/local/bin/python New python executable in /path/to/python_project/.direnv/python-2.7.12/bin/python2.7 Also creating executable in /path/to/python_project/.direnv/python-2.7.12/bin/python Installing setuptools, pip, wheel...done. direnv: export +VIRTUAL_ENV ~PATH
仮想環境が再作成されます。最後にpip install -r
で書き出したパッケージ情報から、パッケージを再インストールすれば、環境が復元できました。
$ pip install -r requirements.txt
おわりに
pythonに複数バージョンが必要になったらこの構成にpyenvを足すだけで良さそう。
direnv便利。
CentOSにLet's EncryptのSSL証明書を導入する
やってみたので、手順をまとめました。実施したのはCentOS6+Apache2.2という、やや古い環境ですがCentOS7とかでも基本的には同じはずです。
Let's Encryptの概要
Let's Encryptは無料のSSL証明書を発行する認証局。 特徴は料金無料であるのと、専用のクライアントコマンドを使って証明書の発行を行う点。またワイルドカードの証明書は発行できない。有効期限が3ヶ月で、更新もコマンドによって自動化するのが前提となっています。
証明書を発行する際の要件として、当然ドメイン所有者の認証(本当にそのドメインの所有者か確認を行うこと)が必要になります。通常の認証局はメールなどを使ってこれを行いますが、Let's EncryptはACMEプロトコルという通信プロトコルと専用のクライアントコマンドを使います。ACMEプロトコルについて以下の記事が参考になりました。
Let's Encrypt を支える ACME プロトコル - Block Rockin’ Codes
ざっくり理解したところでは、ACMEプロトコルの内容は難しいものではなく、HTTPをベースに使った証明書の自動発行手順といった感じのものです。
さて、実際にLet's Encryptの証明書を導入するには一般に以下の作業が必要です。
- クライアントコマンドのインストール
- HTTPサーバの用意
- クライアントコマンドを実行して証明書を取得
- 取得した証明書使うためのHTTPサーバの設定
- 自動更新のための設定
以下、順に記述します。
手順
クライアントコマンドのインストール
ACMEプロトコルでLet's Encryptのサーバと通信して証明書発行を行うCLIツールです。ここでは公式が提供しているcertbot-auto
というツールを使います。
シェルスクリプトとPythonで構成されています。このツールの操作自体、基本的にroot特権が必要になるので、以下のインストール作業のコマンドも全てrootユーザで行いました。
# yum install -y epel-release # curl https://dl.eff.org/certbot-auto -o /usr/bin/certbot-auto # chmod a+x /usr/bin/certbot-auto # certbot-auto --os-packages-only --non-interactive
最後のcertbot-auto --os-packages-only --non-interactive
で依存パッケージがインストールされます(pythonのランタイムとか)。
HTTPサーバの用意
証明書発行時にACMEプロトコルはドメインの認証処理(acme challengeというらしい)において、証明書を発行するドメインに対してHTTPでリクエストを投げてきます。 よってHTTPサーバが稼働している必要があります。ここではapache使うことにします。
# yum install -y httpd
インストールしたらドメイン名でアクセスできるように設定しておきます。
クライアントコマンドを実行して証明書を取得
certbot-auto certonly
で証明書を取得します。証明書発行に必要な秘密鍵などもこのコマンドを叩くだけでまとめて生成されます。
# certbot-auto certonly --non-interactive --agree-tos --webroot -w /var/www/html -d example.com --email kohki.makimoto@gmail.com
以下のオプションは環境に合わせて読み替えてください。
-d
: ドメイン名を指定します。--email
: 更新期限が近づいたときにメール通知されるメアドを指定します。-w
: HTTPサーバのドキュメントルートを指定します。
-w
オプションについて補足します。前述したようにACMEプロトコルはドメインの認証処理でHTTPサーバにアクセスしてくるのですが、その際/.well-known/acme-challenge/
というパスに対してアクセスしてきます。クライアントコマンドはこのパスにLet's Encrypt が指定したテキストを配置する必要があるので、そこにアクセスできるようドキュメントルートを指定するわけです。
その他オプションについての詳細はcertbot-auto --help all
で確認してください。
うまくいくと
/etc/letsencrypt/live/{ドメイン名}
というパスに証明書や秘密鍵が生成されます。
取得した証明書使うためのHTTPサーバの設定
最近はSNIを使ってバーチャルホストごとにSSL設定ができるので、SSL証明書の設定はバーチャルホストごとに設定しました。
<VirtualHost *:443> ServerName example.com # ... SSLEngine on SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem </VirtualHost>
なお、サーバ全体のSSL設定は/etc/httpd/conf.d/ssl.conf
などに以下のように記述しておきます。
LoadModule ssl_module modules/mod_ssl.so AddType application/x-x509-ca-cert .crt AddType application/x-pkcs7-crl .crl SSLPassPhraseDialog builtin SSLSessionCache shmcb:/var/cache/mod_ssl/scache(512000) SSLSessionCacheTimeout 300 SSLMutex default SSLRandomSeed startup file:/dev/urandom 256 SSLRandomSeed connect builtin SSLCryptoDevice builtin SSLStrictSNIVHostCheck off Listen 443
また、必要に応じてhttpにアクセスがあったらhttpsにリダイレクトさせる設定もいれます。
<VirtualHost *:80> ServerName example.com RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R,L] </VirtualHost>
自動更新のための設定
certbot-auto renew
コマンドで証明書の更新ができます。Let's Encryptの証明書は3ヶ月で有効期限が切れるので、cronに以下のコマンドを仕込みます。この例は出力を捨ててますが、ちゃんと運用する場合はきちんとログに残しましょう。
50 3 * * * root certbot-auto renew --post-hook "/usr/sbin/apachectl graceful" > /dev/null 2>&1
証明書取得時のようにドキュメントルートの指定はしていないので、/etc/letsencrypt/
配下の設定ファイルなどを見て更新しているっぽいです。
–-force-renew
をつけて実行すると有効期限が1ヶ月未満にならなくても、強制的に有効期限を更新します。手動で更新検証するのに使いました。
以上で一通り、作業は完了です。
その他
Basic認証をかけたサイトにLet's Encryptを使う
Basic認証をかけているサイトだとACMEプロトコルのドメイン認証で、HTTPサーバにアクセスできず、証明書取得ができません。
以下の設定を入れて/.well-known/acme-challenge
に穴をあけておくとうまくいきます。
<Location /> Satisfy Any AuthType Basic AuthName "My Private Stuff" AuthUserFile /var/www/example/.htpasswd Require valid-user SetEnvIf Request_URI "/.well-known/acme-challenge" acme-challenge Order Deny,Allow Deny from all Allow from env=acme-challenge </Location>
サードパーティツール
クライアントツールはサードパーティーのGo実装のシングルバイナリもあるのですが、現状見送り。公式のほうがやはり安心感ある。
CentOS6のpythonのバージョンが古い
CentOS6はpythonのバージョンが古いのでcertbot-auto実行時に以下のような警告がでました。が、正常に証明書取得までできたのでまあいいかな、と。。。
/root/.local/share/letsencrypt/lib/python2.6/site-packages/cryptography/__init__.py:26: DeprecationWarning: Python 2.6 is no longer supported by the Python core team, please upgrade your Python. A future version of cryptography will drop support for Python 2.6 DeprecationWarning
いろいろ参考にさせていただいたサイトなど
- Let’s EncryptのSSL証明書で、安全なウェブサイトを公開 - さくらのナレッジ
- Let's Encrypt を支える ACME プロトコル - Block Rockin’ Codes
- Go言語でLet's EncryptのACMEを理解する | SOTA
- Support HTTP Basic Authentication with webroot · Issue #1744 · certbot/certbot · GitHub
ありがとうございました。
Go+LuaでHTMLからPDFを生成するCLIツールを作った
以前、Goでプロビジョニングツールを作った でも書いたように、GoとGopherLuaでCLIツールをいろいろ実装していて、今回もその成果物の一つです。
概要
Goで書かれたワンバイナリで動くPDF生成ツールです。現時点で動作環境は64bitのMacとLinuxのみをサポートしてます(windowsはまだ)。全体的にコードの実装はcofuからコピペして雑に作ったので、使いかたもほぼ同じです。
html2pdf
というコマンドをreleaseページからダウンロードしてPATHの通ったディレクトリに配置し、
local html2pdf = require "html2pdf" local example = html2pdf.pdf "example.pdf" example.options = { page_size = "A4", } example.pages = { input_content = "hello world!" }
のようなLuaのコードをexample.lua
に保存。html2pdf example.lua
を実行する。
$ html2pdf example.lua ==> Starting html2pdf... ==> Loaded 1 pdf config. ==> Evaluating example.pdf output_file: example.pdf ==> Complete!
これでexample.pdf
が出力されます。上記の例だとコンテンツを設定するinput_content
に単純な文字列(hello world!)を指定しているだけですが、実際にはHTMLを書いて使います。
使い方
URLを指定
input_content
の代わりにinput
を使えばURLを指定できます。
local html2pdf = require "html2pdf" local example = html2pdf.pdf "example.pdf" example.pages = { input = "https://github.com/kohkimakimoto/html2pdf" }
複数ページ
pages
には複数のページを設定できます。
example.pages = { { input = "https://github.com/kohkimakimoto/html2pdf" }, { input = "https://github.com/kohkimakimoto/cof" }, }
表紙
cover
で表紙を設定できます。ページの指定と同じくinput_content
かinput
でコンテンツを指定できます。
example.cover = { input_content = "<b>title</b>" -- or -- input = "https://..." }
その他オプション
html2pdfは内部でwkhtmltopdfを使っています。というより、実装的にはwkhtmltopdfにLuaスクリプティング機能を載せたラッパーコマンドになっています。なのでwkhtmltopdfのオプションが使えます。options
で以下のようにして指定します。
example.options = { page_size = "A4", margin_left = 5, margin_top = 5, margin_bottom = 5, margin_right = 5, orientation = "Landscape", -- etc... }
詳しくはwkhtmltopdfのドキュメントを参考にしてください。wkhtmltopdf docs
変数
-var
か-var-file
オプションで実行時にコマンドラインから変数を渡せます。変数はJSONで以下のように指定します。
$ html2pdf example.lua -var='{"output_file": "foo.pdf"}'
スクリプト内ではvar
変数として格納されています。
local html2pdf = require "html2pdf" local example = html2pdf.pdf "example.pdf" example.output_file = var.output_file example.pages = { input = "https://github.com/kohkimakimoto/html2pdf" }
Shebang
#!/usr/bin/env html2pdf
をスクリプトファイルの先頭に記述して実行権限をつければ、直接実行してPDFを吐くスクリプトになります。
Markdown
MarkdownからHTMLを生成するLuaモジュールも組み込んだので、すこしだけLuaでロジックを書けばmarkdownからPDFドキュメントを作ることもできます。以下にサンプルを作ったので参考にしてください。
https://github.com/kohkimakimoto/html2pdf/tree/master/_example
表紙付きドキュメントを生成するサンプルには結果のPDFもアップロードしてあります。
https://github.com/kohkimakimoto/html2pdf/blob/master/_example/doc/doc.pdf
DSLな記法
設定はLuaのDSLスタイルでも記述することもできます。次の2つの例は同じ設定です。もりもりコードを書くのでなければ、DSLのほうが見やすいかもしれません。
pdf "hello.pdf" { pages = { input_content = "hello world!" }, }
PlainなLua
local html2pdf = require "html2pdf" local hello = html2pdf.pdf "hello.pdf" hello.pages = { input_content = "hello world!" }
※DSLで使っているpdf
関数はhtml2pdf.pdf
メソッドのエイリアスになっています。
実装の話
当初、PDF変換部分のwkhtmltopdfを調べていたら、これはCで書かれたプロダクトだったので、Goからcgoをつかって連携しようかと考えて調査したのですが、どうも共有ライブラリをロードするGoのプログラムはスタンドアロンなバイナリにならないようだと判明。あとクロスコンパイル周りで罠があったりとか、Macではcgoをつかってスタティックなビルドができないとか。。。(結構いろいろ調べたのですが、もう詳細忘れた。。。とにかくできそうになかった。自分のスキルが足りない可能性ももちろんありますが)。cgoツライ。
html2pdfを使うマシンにwkhtmltopdfをインストールしておけばいいだけなのですが、これでは当然、スタンドアロンなシングルバイナリとはいえないわけです。これはイヤだなと。
で、wkhtmltopdfコマンド自体がスタンドアロンなバイナリなので、もうこのバイナリ自体をまるごとgo-bindataでGoのコードに埋め込んでおき、実行時に/tmp
ディレクトリ以下にwkhtmltopdfのコマンドファイルを生成して、それをhtml2pdfから叩くという荒業を使うことにしました。これで見た目上はhtml2pdfのみで動いているように見えるし、別途wkhtmltopdfをインストールする必要もない。
自分は何に使っているのか
個人事業主やってるので、クライアントに提出する請求書をPDFで生成するのに使っています。
Mac OSX El Capitanにphpenvをいれる
phpenvをMacにいれました。PHPのビルド時にいろいろエラーが出てツライ目にあいました。いろいろやることあります。詳細は末尾の参考サイトを参照してください。ここにはやった結果だけ記述します。
依存物のインストール。
$ brew install re2c $ brew install openssl $ brew install bison $ brew install libxml2
libzがエラーを出すので以下のコマンドを叩きます。
$ xcode-select --install
phpenv
本体とphp-build
のインストール。phpenv
は知名度の高いCHH/phpenv
ではなくてmadumlao/phpenv
をいれます。こちらのほうがプロダクトとして高品質なので。
$ git clone https://github.com/madumlao/phpenv.git ~/.phpenv $ git clone https://github.com/php-build/php-build.git ~/.phpenv/plugins/php-build
.zshrc
にパスとか必要な環境変数の設定
export PATH="$HOME/.phpenv/bin:$PATH" eval "$(phpenv init -)" export PHP_BUILD_CONFIGURE_OPTS="--with-openssl=$(brew --prefix openssl) --with-libxml-dir=$(brew --prefix libxml2)"
シェルをリロードしてから、phpをインストールします。ひとまずphp.5.6のこの時点での最新版。
$ phpenv install 5.6.23
インストールが成功したら標準で使うphpをそれに設定します。
$ phpenv global 5.6.23 5.6.23
バージョンを確認。
$ php -v PHP 5.6.23 (cli) (built: Aug 4 2016 13:23:46) Copyright (c) 1997-2016 The PHP Group Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies with Xdebug v2.3.3, Copyright (c) 2002-2015, by Derick Rethans
参考サイト
SSL証明書の発行手順
Webサーバとかに配置するSSL証明書。毎度手作業なので自分用にメモ。
いつも「ドメイン名.拡張子」というファイル名で秘密鍵、CSR、証明書を作っています。
作業手順
$ openssl genrsa -out example.co.jp.key 2048
$ openssl req -new -key example.co.jp.key -out example.co.jp.csr
Country Name (2 letter code) [GB]:JP State or Province Name (full name) [Berkshire]:Tokyo Locality Name (eg, city) [Newbury]:shinjuku-ku Organization Name (eg, company) [Internet Widgits Pty Ltd]:your company name Organizational Unit Name (eg, section) []: your section Common Name (eg, your name or your server's hostname) []:example.co.jp Email Address []: Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []:
CSRから自己署名で証明書を作成(本番の時は認証局にCSRを提出)。
$ openssl req -in example.co.jp.csr -key example.co.jp.key -x509 -out example.co.jp.crt
メモ
- 更新時、鍵に変更がなければCSRは使いまわせる。
- そろそろLet's Encryptについても触っておきたい。。。