Java POIでWord(.docx)ファイルを作る

はじめに

事前に以下のライブラリを用意します。

今回のサンプルは以下のjarがあれば動作します。

  • poi-3.13-20150929.jar
  • poi-ooxml-3.13-20150929.jar
  • poi-ooxml-schemas-3.13-20150929.jar
  • xmlbeans-2.6.0.jar

実装例

今回のサンプルでは以下の機能を確認します。

  • 複数の段落を作る
  • 段落にスタイルの異なる文字列を複数配置する
  • 表を作る
  • 表のセルの中に複数の段落を作る
  • 表のセルの中の段落にスタイルの異なる文字列を複数配置する

少しあっさりしていますがまずはこれぐらいで。 動作確認しやすいようにmainメソッドで実行できるようにしてあります。

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;

/**
 *
 * @author tool-taro.com
 */
public class DOCXWriteTest {

    public static void main(String[] args) throws FileNotFoundException, IOException {

        String outputFilePath = "out.docx";
        XWPFDocument document = null;
        XWPFTable table;
        XWPFParagraph paragraph;
        XWPFRun run;
        FileOutputStream fout = null;

        try {
            document = new XWPFDocument();

            //普通の段落を2つ作る
            for (int i = 0; i < 2; i++) {
                paragraph = document.createParagraph();

                //それぞれの段落の中に色の異なるテキストを2種配置する
                //setText内で\nを指定しても改行されないので注意、改行するには必ず段落を作る
                run = paragraph.createRun();
                run.setFontFamily("MS ゴシック");
                run.setText("黒のテキスト");

                run = paragraph.createRun();
                run.setFontFamily("MS ゴシック");
                run.setColor("ff0000");
                run.setText("赤のテキスト");
            }
            //2x2の表を作る
            table = document.createTable(2, 2);
            for (int i = 0; i < 2; i++) {
                for (int j = 0; j < 2; j++) {
                    //それぞれのセルの中に段落を2つ作る
                    for (int k = 0; k < 2; k++) {
                        //セルには初期状態で1つの段落がある(実装が変わるかもしれないので念のため存在数を確認して適切に処理)
                        if (table.getRow(i).getCell(j).getParagraphs().size() > k) {
                            paragraph = table.getRow(i).getCell(j).getParagraphs().get(k);
                        }
                        else {
                            paragraph = table.getRow(i).getCell(j).addParagraph();
                        }

                        //それぞれの段落の中に色の異なるテキストを2種配置する
                        run = paragraph.createRun();
                        run.setFontFamily("MS ゴシック");
                        run.setText("黒のテキスト");

                        run = paragraph.createRun();
                        run.setFontFamily("MS ゴシック");
                        run.setColor("ff0000");
                        run.setText("赤のテキスト");
                    }
                }
            }

            //ファイル出力
            fout = new FileOutputStream(outputFilePath);
            document.write(fout);
        }
        finally {
            if (fout != null) {
                try {
                    fout.close();
                }
                catch (Exception e) {
                }
            }
            if (document != null) {
                try {
                    document.close();
                }
                catch (Exception e) {
                }
            }
        }
    }
}

動作確認

$ javac DOCXWriteTest.java
$ java DOCXWriteTest

作成されたファイルはこんな感じになりました。 無題.png

環境

上記の実装をベースにWebツールも公開しています。 Diff(テキスト差分チェック)|Web便利ツール@ツールタロウ

MuninでTomcatのモニタリング

はじめに

MuninでTomcatモニタリングします。 MuninやTomcatはすでにインストールされている前提で、 Tomcat側の受け入れ設定とMunin側の監視設定をします。

Tomcat本体の設定は、過去の記事の通りで、 Muninはポート8080を経由してTomcat側のデータを取得します。

環境

手順

Tomcat側にユーザ・ユーザ権限の追加

$ vi $TOMCAT_HOME/conf/tomcat-users.xml
<tomcat-users...>
  (省略)
  # 追記
  <role rolename="manager-status"/>
  <user username="munin" password="munin" roles="manager-status"/>
</tomcat-users>

Tomcatを再起動し、監視用のI/Fにアクセスできるか確認します。

$ curl --user munin:munin http://localhost:8080/manager/status?XML=true
<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="/manager/xform.xsl" ?>
<status><jvm><memory free='112838232' total='261750784' max='261750784'/>(省略)</status>

Muninの監視用プラグイン等のインストール・配置

$ yum install munin-java-plugins
$ ln -s /usr/share/munin/plugins/tomcat_access /etc/munin/plugins/
$ ln -s /usr/share/munin/plugins/tomcat_jvm /etc/munin/plugins/
$ ln -s /usr/share/munin/plugins/tomcat_threads /etc/munin/plugins/
$ ln -s /usr/share/munin/plugins/tomcat_volume /etc/munin/plugins/
$ yum install perl-XML-Simple

MuninのTomcat監視設定

$ vi /etc/munin/plugin-conf.d/munin-node
# 設定ファイル追記
[tomcat*]
env.host      localhost
env.port      8080
env.request   /manager/status?XML=true
env.user      munin
env.password  munin
env.timeout   30
env.connector "http-nio-8080"

Muninの設定反映・再起動(必要に応じて)

$ munin-node-configure --shell | sh -x
$ systemctl restart munin-node

動作確認

次回以降のグラフ生成のタイミングでTomcatのグラフが生成されていることを確認します。 1455647583283.jpg

Webツールも公開しています。 Web便利ツール@ツールタロウ

Java FacebookのOGPキャッシュクリア

はじめに

というかHttpClientの使い方、といったところですが・・。 事前に以下のライブラリを用意します。

今回のサンプルは以下のjarがあれば動作します。

  • httpclient-4.5.1.jar
  • httpcore-4.4.4.jar

実装例

サンプルでは、動作確認しやすいようにmainメソッドで実行できるようにしてあります。 結果だけを確認したい場合は、この記事の一番下のリンク先で使えるようにしてありますのでご覧ください。

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.Header;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;

/**
 *
 * @author tool-taro.com
 */
public class ScrapeOgp {

    public static void main(String[] args) throws UnsupportedEncodingException, IOException {

        //クリアしたいURL
        String url = "http://.../";
        //ユーザエージェント
        String userAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0";
        //タイムアウト(ミリ秒)
        int timeout = 10000;

        //クリアリクエスト処理
        ByteArrayOutputStream bout = new ByteArrayOutputStream();

        List<Header> headers = new ArrayList<>();
        if (userAgent != null) {
            headers.add(new BasicHeader("User-Agent", userAgent));
        }
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(timeout).setConnectTimeout(timeout).setCircularRedirectsAllowed(true).setRedirectsEnabled(true).build();
        CloseableHttpClient httpclient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setDefaultHeaders(headers).build();

        HttpPost httpPost = new HttpPost("https://graph.facebook.com/");
        List<NameValuePair> requestParams = new ArrayList<>();
        requestParams.add(new BasicNameValuePair("id", url));
        requestParams.add(new BasicNameValuePair("scrape", "true"));
        httpPost.setEntity(new UrlEncodedFormEntity(requestParams));
        CloseableHttpResponse response = null;

        BufferedInputStream in = null;

        try {
            response = httpclient.execute(httpPost);
            in = new BufferedInputStream(response.getEntity().getContent());
            byte[] buf = new byte[1024];
            int length;
            while (true) {
                length = in.read(buf);
                if (length == -1) {
                    break;
                }
                bout.write(buf, 0, length);
            }
        }
        finally {
            if (in != null) {
                try {
                    in.close();
                }
                catch (Exception e) {
                }
            }
            if (response != null) {
                try {
                    response.close();
                }
                catch (Exception e) {
                }
            }
        }

        //標準出力
        System.out.format("リクエスト結果=%1$s", new String(bout.toByteArray(), "UTF-8"));
    }
}

動作確認

$ javac ScrapeOgp.java
$ java ScrapeOgp
$ リクエスト結果={
   "url": ...(省略)

なお、リクエストを送信しても、Facebook側の状況次第では受け付けられない・反映されないケースがあります。ご注意ください。

環境

上記の実装をベースにWebツールも公開しています。 FacebookのOGPキャッシュクリア|Web便利ツール@ツールタロウ

Cloudnの仮想サーバー(FLATタイプ)プラン vQ (月々税抜450円)で固定IPアドレスをゲットする(OpenVPNサーバを作る)

はじめに

前回(さくらのVPS512(月々税抜635円)で固定IPアドレスをゲットする(OpenVPNサーバを作る))に引き続き、今回は非常に安価な最小プランで知られるCloudnVPNサーバを立ち上げる手順をまとめます。

作業前提(自己責任でご判断ください)

  • 今回は「Cloudnの仮想サーバー(FLATタイプ)プラン vQ (月々税抜450円 2016/02/15現在)」を使う
  • CentOS7.1
  • OpenVPN
  • ほかの機能(Webサーバ等)は一切設けない
  • 複数人で使えるようにする
  • 説明の簡略化のため、ほぼすべてrootで作業
  • コマンドの入力、viによるファイルの編集などがあり、漏れなく作業が必要
  • 契約・請求・キャンセル方法等については、エヌ・ティ・ティ・コミュニケーションズ株式会社からの案内に従ってください

問題なければ始めましょう。

作業手順

アカウント登録

パブリッククラウドサービス Cloudnの「サービス申込み」ページからアカウント・決済方法などを登録してください。手順は省略します。

ログイン

ポータルサイトからログインして「FLATタイプ Compute」のコンソールに入ります。 1455502610487.jpg

ネットワークの設定(セキュリティグループ)

メニューの「ネットワーク」を選び、ビューの選択で「セキュリティグループ」を選んでください。 1455502654651.jpg

一覧に表示されている「default」をクリックし、図のように設定を追加します。 1455502709358.jpg

サーバの追加

メニューの「仮想サーバー」を選び、「+仮想サーバーの追加」を選んでください。 1455502740547.jpg

以下のように順に次に進みます。 1455502760157.jpg 1455502784736.jpg 1455502799522.jpg 1455502814541.jpg 1455502832629.jpg 1455502849780.jpg

仮想サーバの追加が終わると、図のようなダイアログが出て、rootのパスワードが通知されます。1度しか通知されませんので必ずメモしておきましょう。 messageImage_1455502922003.jpg

追加されたサーバの詳細からIPアドレスも確認します。 1455502987207.jpg

SSHログイン

ここから先はSSH経由で作業します。 WindowsならTeraTermなど、Macならターミナルなどでログインしてください。 ログイン先のサーバは上記のサーバの詳細で確認したIPアドレス、ユーザはroot、パスワードはサーバ追加時に設定したパスワードです。

rootパスワードの設定

ここから先はすべてrootで作業します。 先ほど設定したrootのパスワードが気に入らない場合は修正しておきましょう。

$ passwd root
Changing password for user root.
New password:パスワード入力
Retype new password:パスワード再入力
passwd: all authentication tokens updated successfully.

SELinuxの無効化

"SELINUX=disabled"となっている場合は変更の必要がありません。先に進みましょう。

$ vi /etc/selinux/config
#SELINUX=enforcing
SELINUX=disabled

SSHログイン用ユーザの追加

SSHでログインする際、最初からrootでログインするのはセキュアではないため、 別のユーザを作り、パスワードを設定します。 今後は、別ユーザでログインしてからrootになる、という流れを作るわけです。

$ useradd sshuser
$ passwd sshuser
Changing password for user sshuser.
New password:パスワード入力
Retype new password:パスワード再入力
passwd: all authentication tokens updated successfully.

rootでのSSHログインの無効化

SSHでログインする際、rootでのログインを許可しないように設定します。

$ vi /etc/ssh/sshd_config
#PermitRootLogin yes
PermitRootLogin no

変更を反映します。

$ systemctl restart sshd.service

次回以降のログインの流れについて触れておきます。 先ほど作ったsshuserでログインした後、rootになるには以下のように入力します。

$ su -
Password:パスワード入力

sshuserでログインする流れを確認したところで、 SELinuxの設定変更の反映もしておきたいので、サーバを再起動します。

$ reboot

再度SSHでアクセスできるようになったら、sshuserでログインし、rootになります。

IPフォワーディングの有効化

$ vi /etc/sysctl.d/10-ipv4.conf
#追記
net.ipv4.ip_forward = 1

変更を反映します。

$ sysctl -p /etc/sysctl.d/10-ipv4.conf
net.ipv4.ip_forward = 1

openvpnのインストール

$ yum -y install epel-release
$ yum -y install openvpn
$ yum -y install unzip

この後、設定をしていくわけですが、先にパスワードを1つ決めておいてください。 各種証明書の作成時などに使います。 パスワード(A)とします。

CA証明書・秘密鍵の作成

$ cd /usr/local/src/
$ wget https://github.com/OpenVPN/easy-rsa/archive/master.zip
$ unzip master.zip
$ cp -r easy-rsa-master/easyrsa3/ /etc/openvpn/
$ rm -fr easy-rsa-master/
$ cd /etc/openvpn/easyrsa3/
$ ./easyrsa init-pki

init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /etc/openvpn/easyrsa3/pki

$ ./easyrsa build-ca
Generating a 2048 bit RSA private key
............................................................................................+++
.................................................+++
writing new private key to '/etc/openvpn/easyrsa3/pki/private/ca.key.XXXXXXXXXX'
Enter PEM pass phrase:パスワード(A)入力
Verifying - Enter PEM pass phrase:パスワード(A)再入力
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Common Name (eg: your user, host, or server name) [Easy-RSA CA]:サーバ名入力(例: vpnserver)

CA creation complete and you may now import and sign cert requests.
Your new CA certificate file for publishing is at:
/etc/openvpn/easyrsa3/pki/ca.crt

$ cp pki/ca.crt /etc/openvpn/

サーバ証明書秘密鍵の作成

$ ./easyrsa build-server-full server nopass
Generating a 2048 bit RSA private key
...................................................+++
...............................................................................+++
writing new private key to '/etc/openvpn/easyrsa3/pki/private/server.key.e7omRJmwXu'
-----
Using configuration from /etc/openvpn/easyrsa3/openssl-1.0.cnf
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:パスワード(A)入力
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :PRINTABLE:'server'
Certificate is to be certified until Feb  3 04:11:41 2026 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated

$ cp pki/issued/server.crt /etc/openvpn/
$ cp pki/private/server.key /etc/openvpn/

DHパラメータの作成

$ ./easyrsa gen-dh
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
(省略)

DH parameters of size 2048 created at /etc/openvpn/easyrsa3/pki/dh.pem

$ cp pki/dh.pem /etc/openvpn/

クライアント証明書(ダミー)の作成

$ ./easyrsa build-client-full dmy nopass
Generating a 2048 bit RSA private key
..................+++
..................+++
writing new private key to '/etc/openvpn/easyrsa3/pki/private/dmy.key.XXXXXXXXXX'
-----
Using configuration from /etc/openvpn/easyrsa3/openssl-1.0.cnf
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:パスワード(A)入力
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :PRINTABLE:'dmy'
Certificate is to be certified until Feb  3 04:14:46 2026 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated

$ ./easyrsa revoke dmy


Please confirm you wish to revoke the certificate with the following subject:

subject=
    commonName                = dmy


Type the word 'yes' to continue, or any other input to abort.
  Continue with revocation: yes
Using configuration from /etc/openvpn/easyrsa3/openssl-1.0.cnf
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:パスワード(A)入力
Revoking Certificate 02.
Data Base Updated

IMPORTANT!!!

Revocation was successful. You must run gen-crl and upload a CRL to your
infrastructure in order to prevent the revoked cert from being accepted.

$ ./easyrsa gen-crl
Using configuration from /etc/openvpn/easyrsa3/openssl-1.0.cnf
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:パスワード(A)入力

An updated CRL has been created.
CRL file: /etc/openvpn/easyrsa3/pki/crl.pem

$ cp pki/crl.pem /etc/openvpn/
$ chmod o+r /etc/openvpn/crl.pem

OpenVPNの設定

$ vi /etc/openvpn/server.conf
port 1194
proto udp
dev tun

ca ca.crt
cert server.crt
key server.key  # This file should be kept secret
dh dh.pem

server 192.168.100.0 255.255.255.0

ifconfig-pool-persist ipp.txt

push "redirect-gateway def1"

fragment 1280
mssfix 1280
link-mtu 1400

client-to-client

keepalive 10 120

comp-lzo

user nobody
group nobody

persist-key
persist-tun

status openvpn-status.log

log-append  /var/log/openvpn.log

verb 3

crl-verify crl.pem

サービスの登録・起動

$ systemctl enable openvpn@server.service
Created symlink from /etc/systemd/system/multi-user.target.wants/openvpn@server.service to /usr/lib/systemd/system/openvpn@.service.

$ systemctl start openvpn@server.service

Firewallの起動・設定

$ systemctl enable firewalld
Created symlink from /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service to /usr/lib/systemd/system/firewalld.service.
Created symlink from /etc/systemd/system/basic.target.wants/firewalld.service to /usr/lib/systemd/system/firewalld.service.

$ systemctl start firewalld

$ firewall-cmd --permanent --zone=public --add-masquerade
$ firewall-cmd --permanent --zone=public --add-port=1194/udp
$ firewall-cmd --reload
$ firewall-cmd --list-all
public (default, active)
  interfaces: eth0
  sources:
  services: dhcpv6-client ssh
  ports: 1194/udp
  masquerade: yes
  forward-ports:
  icmp-blocks:
  rich rules:

クライアント証明書・秘密鍵の作成

複数ユーザ分を作る場合は繰り返してください。

$ cd /etc/openvpn/easyrsa3/
$ ./easyrsa build-client-full ユーザ名
Generating a 2048 bit RSA private key
...............................................................+++
...................................+++
writing new private key to '/etc/openvpn/easyrsa3/pki/private/ユーザ名.key.XXXXXXXXXX'
Enter PEM pass phrase:ユーザ個別のパスワード入力
Verifying - Enter PEM pass phrase:ユーザ個別のパスワード再入力
-----
Using configuration from /etc/openvpn/easyrsa3/openssl-1.0.cnf
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:パスワード(A)入力
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :T61STRING:'ユーザ名'
Certificate is to be certified until Feb  3 04:25:02 2026 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated

$ cp /etc/openvpn/ca.crt /home/sshuser/
$ cp /etc/openvpn/easyrsa3/pki/issued/ユーザ名.crt /home/sshuser/
$ cp /etc/openvpn/easyrsa3/pki/private/ユーザ名.key /home/sshuser/
$ chown sshuser:sshuser /home/sshuser/ca.crt
$ chown sshuser:sshuser /home/sshuser/ユーザ名.crt
$ chown sshuser:sshuser /home/sshuser/ユーザ名.key

各証明書・秘密鍵のユーザへの配布

一つ前の手順で/home/sshuser以下に設置しましたので、 SFTPなどでログインしてダウンロードするとよいでしょう。

ca.crt
ユーザ名.crt
ユーザ名.key

また、下記の情報も伝えます。

ユーザ名
ユーザ個別のパスワード

クライアントの接続設定

過去の記事を参考にしてください。

接続の確認

接続が完了したら、自分のIPアドレス確認用のサイトに行って確認してみましょう。 Cloudnで与えられたサーバのIPアドレスになっていたら成功です。 IPアドレス確認|Web便利ツール@ツールタロウ IPアドレス確認|Web便利ツール@ツールタロウ.png

さいごに

必要最小限の作業にとどめたつもりでいるのですが、課題は色々あると思います。

例えば、

  • SSHはどこからでも接続できてしまう(→Firewallで閉じ、普段はCloudnのコンソールから作業する等の対応は可能)
  • このサーバの状態を監視する手段がない

などです。

適宜、環境に合わせて追加・変更していただき、快適な運用をしていただければ幸いです。 ここまでお付き合いいただきましてありがとうございました。

Webツールも公開しています。 Web便利ツール@ツールタロウ

ConoHaの最小プラン(月々税抜900円)で固定IPアドレスをゲットする(OpenVPNサーバを作る)

はじめに

前回(さくらのVPS512(月々税抜635円)で固定IPアドレスをゲットする(OpenVPNサーバを作る))に引き続き、今回はパフォーマンスが評判のConoHaVPNサーバを立ち上げる手順をまとめます。

作業前提(自己責任でご判断ください)

  • 今回は「ConoHa 最小プラン(月々税抜900円 2016/02/15現在)」を使う
  • CentOS7.2
  • OpenVPN
  • ほかの機能(Webサーバ等)は一切設けない
  • 複数人で使えるようにする
  • 説明の簡略化のため、ほぼすべてrootで作業
  • コマンドの入力、viによるファイルの編集などがあり、漏れなく作業が必要
  • 契約・請求・キャンセル方法等については、GMOインターネット株式会社からの案内に従ってください

問題なければ始めましょう。

作業手順

アカウント登録

ConoHaの「お申し込み」ページからアカウント・決済方法などを登録してください。手順は省略します。 1455478838393.jpg

ログイン・サーバ追加

ログインしてメニューの「サーバー」をクリックして、「サーバーリスト」を表示させてみます。 右上の「+サーバー」でサーバを追加しましょう。 1455477214824.jpg

設定は下記のようにセットしてください。今回はIPv6を使用しないようにしています。 1455477281696.jpg

サーバ一覧の確認

サーバの追加はあっという間に終わります。 1455477353720.jpg

サーバの詳細を確認してみましょう。 1455477389424.jpg

SSHログイン

ここから先はSSH経由で作業します。 WindowsならTeraTermなど、Macならターミナルなどでログインしてください。 ログイン先のサーバは上記のサーバの詳細で確認したIPアドレス、ユーザはroot、パスワードはサーバ追加時に設定したパスワードです。

rootパスワードの設定

ここから先はすべてrootで作業します。 先ほど設定したrootのパスワードが気に入らない場合は修正しておきましょう。

$ passwd root
Changing password for user root.
New password:パスワード入力
Retype new password:パスワード再入力
passwd: all authentication tokens updated successfully.

SELinuxの無効化

"SELINUX=disabled"となっている場合は変更の必要がありません。先に進みましょう。

$ vi /etc/selinux/config
SELINUX=disabled

SSHログイン用ユーザの追加

SSHでログインする際、最初からrootでログインするのはセキュアではないため、 別のユーザを作り、パスワードを設定します。 今後は、別ユーザでログインしてからrootになる、という流れを作るわけです。

$ useradd sshuser
$ passwd sshuser
Changing password for user sshuser.
New password:パスワード入力
Retype new password:パスワード再入力
passwd: all authentication tokens updated successfully.

rootでのSSHログインの無効化

SSHでログインする際、rootでのログインを許可しないように設定します。

$ vi /etc/ssh/sshd_config
#PermitRootLogin yes
PermitRootLogin no

変更を反映します。

$ systemctl restart sshd.service

次回以降のログインの流れについて触れておきます。 先ほど作ったsshuserでログインした後、rootになるには以下のように入力します。

$ su -
Password:パスワード入力

sshuserでログインする流れを確認したところで、 SELinuxの設定変更の反映もしておきたいので、サーバを再起動します。

$ reboot

再度SSHでアクセスできるようになったら、sshuserでログインし、rootになります。

IPフォワーディングの有効化

$ vi /etc/sysctl.d/10-ipv4.conf
#追記
net.ipv4.ip_forward = 1

変更を反映します。

$ sysctl -p /etc/sysctl.d/10-ipv4.conf
net.ipv4.ip_forward = 1

openvpnのインストール

$ yum -y install openvpn

この後、設定をしていくわけですが、先にパスワードを1つ決めておいてください。 各種証明書の作成時などに使います。 パスワード(A)とします。

CA証明書・秘密鍵の作成

$ cd /usr/local/src/
$ wget https://github.com/OpenVPN/easy-rsa/archive/master.zip
$ unzip master.zip
$ cp -r easy-rsa-master/easyrsa3/ /etc/openvpn/
$ rm -fr easy-rsa-master/
$ cd /etc/openvpn/easyrsa3/
$ ./easyrsa init-pki

init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /etc/openvpn/easyrsa3/pki

$ ./easyrsa build-ca
Generating a 2048 bit RSA private key
............................................................................................+++
.................................................+++
writing new private key to '/etc/openvpn/easyrsa3/pki/private/ca.key.XXXXXXXXXX'
Enter PEM pass phrase:パスワード(A)入力
Verifying - Enter PEM pass phrase:パスワード(A)再入力
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Common Name (eg: your user, host, or server name) [Easy-RSA CA]:サーバ名入力(例: vpnserver)

CA creation complete and you may now import and sign cert requests.
Your new CA certificate file for publishing is at:
/etc/openvpn/easyrsa3/pki/ca.crt

$ cp pki/ca.crt /etc/openvpn/

サーバ証明書秘密鍵の作成

$ ./easyrsa build-server-full server nopass
Generating a 2048 bit RSA private key
...................................................+++
...............................................................................+++
writing new private key to '/etc/openvpn/easyrsa3/pki/private/server.key.e7omRJmwXu'
-----
Using configuration from /etc/openvpn/easyrsa3/openssl-1.0.cnf
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:パスワード(A)入力
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :PRINTABLE:'server'
Certificate is to be certified until Feb  3 04:11:41 2026 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated

$ cp pki/issued/server.crt /etc/openvpn/
$ cp pki/private/server.key /etc/openvpn/

DHパラメータの作成

$ ./easyrsa gen-dh
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
(省略)

DH parameters of size 2048 created at /etc/openvpn/easyrsa3/pki/dh.pem

$ cp pki/dh.pem /etc/openvpn/

クライアント証明書(ダミー)の作成

$ ./easyrsa build-client-full dmy nopass
Generating a 2048 bit RSA private key
..................+++
..................+++
writing new private key to '/etc/openvpn/easyrsa3/pki/private/dmy.key.XXXXXXXXXX'
-----
Using configuration from /etc/openvpn/easyrsa3/openssl-1.0.cnf
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:パスワード(A)入力
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :PRINTABLE:'dmy'
Certificate is to be certified until Feb  3 04:14:46 2026 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated

$ ./easyrsa revoke dmy


Please confirm you wish to revoke the certificate with the following subject:

subject=
    commonName                = dmy


Type the word 'yes' to continue, or any other input to abort.
  Continue with revocation: yes
Using configuration from /etc/openvpn/easyrsa3/openssl-1.0.cnf
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:パスワード(A)入力
Revoking Certificate 02.
Data Base Updated

IMPORTANT!!!

Revocation was successful. You must run gen-crl and upload a CRL to your
infrastructure in order to prevent the revoked cert from being accepted.

$ ./easyrsa gen-crl
Using configuration from /etc/openvpn/easyrsa3/openssl-1.0.cnf
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:パスワード(A)入力

An updated CRL has been created.
CRL file: /etc/openvpn/easyrsa3/pki/crl.pem

$ cp pki/crl.pem /etc/openvpn/
$ chmod o+r /etc/openvpn/crl.pem

OpenVPNの設定

$ vi /etc/openvpn/server.conf
port 1194
proto udp
dev tun

ca ca.crt
cert server.crt
key server.key  # This file should be kept secret
dh dh.pem

server 192.168.100.0 255.255.255.0

ifconfig-pool-persist ipp.txt

push "redirect-gateway def1"
push "dhcp-option DNS 157.7.180.133" # ConoHa DNS1
push "dhsp-option DNS 210.157.3.4"   # ConoHa DNS2

fragment 1280
mssfix 1280
link-mtu 1400

client-to-client

keepalive 10 120

comp-lzo

user nobody
group nobody

persist-key
persist-tun

status openvpn-status.log

log-append  /var/log/openvpn.log

verb 3

crl-verify crl.pem

サービスの登録・起動

$ systemctl enable openvpn@server.service
Created symlink from /etc/systemd/system/multi-user.target.wants/openvpn@server.service to /usr/lib/systemd/system/openvpn@.service.

$ systemctl start openvpn@server.service

Firewallの設定

$ firewall-cmd --permanent --zone=public --add-masquerade
$ firewall-cmd --permanent --zone=public --add-port=1194/udp
$ firewall-cmd --reload
$ firewall-cmd --list-all
public (default, active)
  interfaces: eth0
  sources:
  services: dhcpv6-client ssh
  ports: 1194/udp
  masquerade: yes
  forward-ports:
  icmp-blocks:
  rich rules:

クライアント証明書・秘密鍵の作成

複数ユーザ分を作る場合は繰り返してください。

$ cd /etc/openvpn/easyrsa3/
$ ./easyrsa build-client-full ユーザ名
Generating a 2048 bit RSA private key
...............................................................+++
...................................+++
writing new private key to '/etc/openvpn/easyrsa3/pki/private/ユーザ名.key.XXXXXXXXXX'
Enter PEM pass phrase:ユーザ個別のパスワード入力
Verifying - Enter PEM pass phrase:ユーザ個別のパスワード再入力
-----
Using configuration from /etc/openvpn/easyrsa3/openssl-1.0.cnf
Enter pass phrase for /etc/openvpn/easyrsa3/pki/private/ca.key:パスワード(A)入力
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :T61STRING:'ユーザ名'
Certificate is to be certified until Feb  3 04:25:02 2026 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated

$ cp /etc/openvpn/ca.crt /home/sshuser/
$ cp /etc/openvpn/easyrsa3/pki/issued/ユーザ名.crt /home/sshuser/
$ cp /etc/openvpn/easyrsa3/pki/private/ユーザ名.key /home/sshuser/
$ chown sshuser:sshuser /home/sshuser/ca.crt
$ chown sshuser:sshuser /home/sshuser/ユーザ名.crt
$ chown sshuser:sshuser /home/sshuser/ユーザ名.key

各証明書・秘密鍵のユーザへの配布

一つ前の手順で/home/sshuser以下に設置しましたので、 SFTPなどでログインしてダウンロードするとよいでしょう。

ca.crt
ユーザ名.crt
ユーザ名.key

また、下記の情報も伝えます。

ユーザ名
ユーザ個別のパスワード

クライアントの接続設定

過去の記事を参考にしてください。

接続の確認

接続が完了したら、自分のIPアドレス確認用のサイトに行って確認してみましょう。 ConoHaで与えられたサーバのIPアドレスになっていたら成功です。 IPアドレス確認|Web便利ツール@ツールタロウ IPアドレス確認|Web便利ツール@ツールタロウ.png

さいごに

必要最小限の作業にとどめたつもりでいるのですが、課題は色々あると思います。

例えば、

  • SSHはどこからでも接続できてしまう(→Firewallで閉じ、普段はConoHaのコンソールから作業する等の対応は可能)
  • このサーバの状態を監視する手段がない

などです。

適宜、環境に合わせて追加・変更していただき、快適な運用をしていただければ幸いです。 ここまでお付き合いいただきましてありがとうございました。

Webツールも公開しています。 Web便利ツール@ツールタロウ

Java AA→画像 (画像→AAの続き)

はじめに

前回、画像からアスキーアートを生成しましたので、 続きでアスキーアートを画像に戻します。

実装例

サンプルでは、動作確認しやすいようにmainメソッドで実行できるようにしてあります。 ソースコードは前回のものに処理を付け足す形としました("ここから追加分"の記述箇所以降)。 結果だけを確認したい場合は、この記事の一番下のリンク先で使えるようにしてありますのでご覧ください。

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.imageio.ImageIO;

/**
 *
 * @author tool-taro.com
 */
public class ImageToAscii {

    public static void main(String[] args) throws IOException, InterruptedException {

        //Javaのパス
        //String javaPath = "/usr/java/latest/bin/java";
        String javaPath = "C:\\Program Files\\Java\\jdk1.8.0_74\\bin\\java.exe";
        //jave5のディレクトリ
        String javeDir = "/usr/local/jave5";
        //jave5.jarのパス
        String javePath = "/usr/local/jave5/jave5.jar";

        //読み取りたい画像ファイルの保存場所
        String inputFilePath = "input.jpg";
        //Ascii文字数(横)
        int width = 200;

        String[] commandArray = new String[6];
        int index = 0;
        commandArray[index++] = javaPath;
        commandArray[index++] = "-jar";
        commandArray[index++] = javePath;
        commandArray[index++] = "image2ascii";
        commandArray[index++] = inputFilePath;
        commandArray[index++] = "width=" + width;

        Runtime runtime = Runtime.getRuntime();
        Process process = null;
        StringBuilder logBuilder = new StringBuilder();
        StringBuilder errorBuilder = new StringBuilder();
        int status = 0;

        try {
            //作業ディレクトリをjave5ディレクトリに移して処理する
            process = runtime.exec(commandArray, null, new File(javeDir));
            final InputStream in = process.getInputStream();
            final InputStream ein = process.getErrorStream();

            Runnable inputStreamThread = () -> {
                BufferedReader reader = null;
                String line;
                try {
                    reader = new BufferedReader(new InputStreamReader(in));
                    while (true) {
                        line = reader.readLine();
                        if (line == null) {
                            break;
                        }
                        logBuilder.append(line).append("\n");
                    }
                }
                catch (Exception e) {
                }
                finally {
                    if (reader != null) {
                        try {
                            reader.close();
                        }
                        catch (Exception e) {
                        }
                    }
                }
            };
            Runnable errorStreamThread = () -> {
                BufferedReader reader = null;
                String line;
                try {
                    reader = new BufferedReader(new InputStreamReader(ein));
                    while (true) {
                        line = reader.readLine();
                        if (line == null) {
                            break;
                        }
                        errorBuilder.append(line).append("\n");
                    }
                }
                catch (Exception e) {
                }
                finally {
                    if (reader != null) {
                        try {
                            reader.close();
                        }
                        catch (Exception e) {
                        }
                    }
                }
            };

            Thread inThread = new Thread(inputStreamThread);
            Thread errorThread = new Thread(errorStreamThread);

            inThread.start();
            errorThread.start();

            status = process.waitFor();
            inThread.join();
            errorThread.join();
        }
        finally {
            if (process != null) {
                try {
                    process.destroy();
                }
                catch (Exception e) {
                }
            }
        }
        System.out.format("変換結果\n%1$s", logBuilder.toString());

        //ここから追加分
        //AA→画像ファイルの保存場所
        String outputFilePath = "ascii.png";

        //標準の等幅フォントを使う
        String fontName = Font.MONOSPACED;
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        Font[] fonts = ge.getAllFonts();
        for (Font font : fonts) {
            //MSゴシックがある環境であれば優先して使う
            if ("MS Gothic".equals(font.getName())) {
                fontName = font.getFontName();
                break;
            }
        }
        Font font = new Font(fontName, Font.PLAIN, 12);

        //Graphics・FontMetricsを取得するためのダミー
        BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_USHORT_GRAY);
        Graphics2D graphics = image.createGraphics();
        FontMetrics metrics = graphics.getFontMetrics(font);

        //文字間のピクセル(調整値)
        int colMargin = 0;
        //行間のピクセル(調整値)
        int rowMargin = 0;
        //等幅フォントの幅を取得する
        int fontWidth = metrics.charWidth(' ');
        //等幅フォントの高さを取得する(FontMetrics#getHeightは行間のサイズまで入ってしまうので使わない)
        int fontHeight = metrics.getDescent() + metrics.getAscent();

        String[] lines = logBuilder.toString().split("\n");
        //フォントの幅・高さから計算されるImageを生成する
        image = new BufferedImage(fontWidth * width + (width - 1) * colMargin, fontHeight * lines.length + (lines.length - 1) * rowMargin, BufferedImage.TYPE_USHORT_GRAY);
        graphics = image.createGraphics();
        //背景を白に
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
        //文字を黒に
        graphics.setColor(Color.BLACK);
        //フォントも忘れず指定
        graphics.setFont(font);
        //文字間の調整がある場合は1文字ずつ位置を決めながら描画する
        if (colMargin > 0) {
            for (int i = 0; i < lines.length; i++) {
                for (int j = 0; j < lines[i].length(); j++) {
                    //描画したい行の一番上から描画するのではなく、FontMetrics#getAscentで得られたベースラインを基準に描画する
                    graphics.drawString(String.valueOf(lines[i].charAt(j)), (fontWidth + colMargin) * j, (fontHeight + rowMargin) * i + metrics.getAscent());
                }
            }
        }
        //文字間の調整がない場合はまとめて描画する
        else {
            for (int i = 0; i < lines.length; i++) {
                //描画したい行の一番上から描画するのではなく、FontMetrics#getAscentで得られたベースラインを基準に描画する
                graphics.drawString(lines[i], 0, (fontHeight + rowMargin) * i + metrics.getAscent());
            }
        }
        ImageIO.write(image, "png", new File(outputFilePath));
    }
}

動作確認

$ javac ImageToAscii.java
$ java ImageToAscii
(AAが出力されるため省略)

変換前の画像は以下のファイルです。 c43ffd45-ca14-9958-b185-882ee7d4e40b.jpeg

今回はWindowsCentOSで実行し、フォントの違いによる差を確認しました。

Windows ascii.png

CentOS linux.png

環境

上記の実装をベースにWebツールも公開しています。 AA変換(アスキーアート生成)|Web便利ツール@ツールタロウ

Java 画像→AA

はじめに

画像をアスキーアートに変換します。 事前に以下のアプリケーションを用意します。

ライブラリとして使う方法が見当たらなかったため、Runtime経由でjavaコマンドで起動します。 変換前の画像は変換後の状態と並べて後述します。

実装例

サンプルでは、動作確認しやすいようにmainメソッドで実行できるようにしてあります。 結果だけを確認したい場合は、この記事の一番下のリンク先で使えるようにしてありますのでご覧ください。

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 *
 * @author tool-taro.com
 */
public class ImageToAscii {

    public static void main(String[] args) throws IOException, InterruptedException {

        //Javaのパス
        String javaPath = "/usr/java/latest/bin/java";
        //jave5のディレクトリ
        String javeDir = "/usr/local/jave5";
        //jave5.jarのパス
        String javePath = "/usr/local/jave5/jave5.jar";

        //読み取りたい画像ファイルの保存場所
        String inputFilePath = "input.jpg";
        //Ascii文字数(横)
        int width = 200;

        String[] commandArray = new String[6];
        int index = 0;
        commandArray[index++] = javaPath;
        commandArray[index++] = "-jar";
        commandArray[index++] = javePath;
        commandArray[index++] = "image2ascii";
        commandArray[index++] = inputFilePath;
        commandArray[index++] = "width=" + width;

        Runtime runtime = Runtime.getRuntime();
        Process process = null;
        StringBuilder logBuilder = new StringBuilder();
        StringBuilder errorBuilder = new StringBuilder();
        int status = 0;

        try {
            //作業ディレクトリをjave5ディレクトリに移して処理する
            process = runtime.exec(commandArray, null, new File(javeDir));
            final InputStream in = process.getInputStream();
            final InputStream ein = process.getErrorStream();

            Runnable inputStreamThread = () -> {
                BufferedReader reader = null;
                String line;
                try {
                    reader = new BufferedReader(new InputStreamReader(in));
                    while (true) {
                        line = reader.readLine();
                        if (line == null) {
                            break;
                        }
                        logBuilder.append(line).append("\n");
                    }
                }
                catch (Exception e) {
                }
                finally {
                    if (reader != null) {
                        try {
                            reader.close();
                        }
                        catch (Exception e) {
                        }
                    }
                }
            };
            Runnable errorStreamThread = () -> {
                BufferedReader reader = null;
                String line;
                try {
                    reader = new BufferedReader(new InputStreamReader(ein));
                    while (true) {
                        line = reader.readLine();
                        if (line == null) {
                            break;
                        }
                        errorBuilder.append(line).append("\n");
                    }
                }
                catch (Exception e) {
                }
                finally {
                    if (reader != null) {
                        try {
                            reader.close();
                        }
                        catch (Exception e) {
                        }
                    }
                }
            };

            Thread inThread = new Thread(inputStreamThread);
            Thread errorThread = new Thread(errorStreamThread);

            inThread.start();
            errorThread.start();

            status = process.waitFor();
            inThread.join();
            errorThread.join();
        }
        finally {
            if (process != null) {
                try {
                    process.destroy();
                }
                catch (Exception e) {
                }
            }
        }
        System.out.format("変換結果\n%1$s", logBuilder.toString());
    }
}

動作確認

$ javac ImageToAscii.java
$ java ImageToAscii
(AAが出力されるため省略)

変換前の画像は以下のファイルです。 c43ffd45-ca14-9958-b185-882ee7d4e40b.jpeg

変換後の文字列はテキストエディタに貼りつけてキャプチャしました。 無題 (2).png

環境

上記の実装をベースにWebツールも公開しています。 AA変換(アスキーアート生成)|Web便利ツール@ツールタロウ