コンテンツへスキップ

文字列のエスケープ処理

PHPでWEB APIを利用していると、制御文字や改行文字を削除(エスケープ)したい場面が出てきます。

  1. 改行文字(x0A x0D)含む制御文字を削除したい場合
  2. 改行文字(x0A x0D)は残し制御文字を削除したい場合

1.改行文字を含む制御文字を削除したい場合

x0A Line Feed(改行)およびx0D Carriage Return(復帰)と制御文字を削除したい場合の、PHPコードです。

$string = preg_replace('/[\x00-\x1F\x7F]/','', $string);

改行文字含むコントロール文字は、POSIXキャラクタクラスにおいて [:cntrl:] で定義されており、以下のように記載することも可能です。

$string = preg_replace('/[[:cntrl:]]/', '', $string);

2.改行文字は残し制御文字を削除したい場合

x0A Line Feed(改行)およびx0D Carriage Return(復帰)と制御文字は残し、その他の制御文字を削除する場合の、PHPコードです。

$string = preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', '', $string);

参考にさせて頂いたサイト

Remove control characters from PHP string

[制御文字一覧]

16進略号名称CS
00NUL空白(Null)^@
01SOHヘッディング開始(Start of Heading)^A
02STXテキスト開始(Start of Text)^B
03ETXテキスト終結(End of Text)^C
04EOT伝送終了(End of Transmission)^D
05ENQ問い合わせ(Enquiry)^E
06ACK肯定応答(Acknowledge)^F
07BELベル(Bell)^G
08BS後退(Backspace)^H
09HT水平タブ(Horizontal Tabulation)^I
0ALF改行(Line Feed)^J
0BVT垂直タブ(Vertical Tabulation)^K
0CFF書式送り(Form Feed)^L
0DCR復帰(Carriage Return)^M
0ESOシフトアウト(Shift Out)^N
0FSIシフトイン(Shift In)^O
10DLE伝送制御拡張(Data Link Escape)^P
11DC1装置制御1(Device Control 1)^Q
12DC2装置制御2(Device Control 2)^R
13DC3装置制御3(Device Control 3)^S
14DC4装置制御4(Device Control 4)^T
15NAK否定応答(Negative Acknowledge)^U
16SYN同期信号(Synchronous Idle)^V
17ETB伝送ブロック終結(End of Transmission Block)^W
18CAN取り消し(Cancel)^X
19EM媒体終結(End of Medium)^Y
1ASUB置換(Substitute Character)^Z
1BESC拡張(Escape)^[
1CFSファイル分離(File Separator)^\
1DGSグループ分離(Group Separator)^]
1ERSレコード分離(Record Separator)^^
1FUSユニット分離(Unit Separator)^_
7FDEL抹消(Delete)^?

[POSIXキャラクタクラス]

クラス名意味
[:alnum:]アルファベットと(十進)数字
[:alpha:]アルファベット
[:blank:]空白文字(スペース、タブ等)
[:cntrl:]制御文字
[:digit:]十進数字
[:graph:]印字可能かつ表示可能な文字(スペースは印字可能だが表示可能ではない)
[:lower:]アルファベットの小文字
[:print:]印字可能なキャラクタ(=制御文字以外のキャラクタ)
[:punct:]句読点(通常の文字、数字、制御文字、スペースのいずれでもないキャラクター)
[:space:]スペース、タブ、改ページ
[:upper:]アルファベットの大文字
[:xdigit:]十六進数字

SendGridのInbound Email Parseを利用

SendGridは、メール配信などで利用されている方は多いのかもしれませんが、メール受信トリガーにWEB API経由でPOSTしてくれるサービスも提供されているようです。メール受信時のWEB APIプッシュの実現方法を探している際に見つけました。
グローバルで、企業向けにサービスを展開されているので、サービス品質などに間違いはなさそうです。
IFTTTでメール受信をしっかりとトリガー出来ていれば本サービスを利用することも無かったかもしれませんが、利用してみてPubSubというような使い方が出来そうなのでメインで利用する予定です。一般向け製品では通知手段がメールというサービスが多いです。

SendGrid | メールを成功の原動力に。

DNS設定に関しては、利用しているVALUE-DOMAIN(バリュードメイン)を例に説明を行います。

概要

sendgridで扱うEmail専用のサブドメインを準備します。サブドメイン宛のメールはすべてSendGridのメールサーバーに送信されるように設定し、SendGridでは、メール受信時のアクション(アクセスするWEB APIのURL)を設定します。あとは、受信を受けたWEB APIがメールの件名や差出人などにより実施したい処理を実施します。
現時点では、モーションセンサーの通知などをメール受信し、各種情報のデータ化としてデーターベースへの蓄積を行っております。

  1. SendGridサービス登録
  2. SendGridサービスで利用ドメインの認証
  3. SendGridでメール受信時の動作設定(メールをPOSTするWEB APIのアドレスを設定)
  4. 自ドメインのSendGridへのメール転送設定(DNS設定)

1. SendGridサービス登録

SendGridのサイトよりアカウントの新規登録を行います。
メールアドレスを登録し、簡単なアンケートや情報入力を行い、本登録を待ちます。人の手でサービス品質を保っているようで、以下メールが来て1日程度登録を待ちました。
実際、住所登録に不備があり、住所修正を行い、本登録が終了しました

SendGridにご登録いただき、誠にありがとうございます。

ログイン用のユーザ名につきましては、ご登録内容の確認およびアカウントのご利用準備が整い次第、別途ご連絡いたします(通常、翌2営業日以内)。
※ユーザ名はメールアドレスではございません。

なお、ご登録内容について確認のご連絡をさせていただく場合がございますので、あらかじめご了承ください。

サービス登録後の、利用者向けポータルのトップページ。

2. SendGridサービスで利用ドメインの認証

SendGrid-Inbound Email Parse Webhookを利用すると、利用条件としてドメインの認証が必要と、ドメイン認証の画面に誘導されます。以下の画面が、まずドメインの認証が必要とダイアログが出ています。

ドメインの認証方法は、利用しているドメインサーバのDNS設定にDNSレコードの追加を行います。SendGridで指定されたDNSレコードを追加する必要があります。SendGrid側でDNS変更を確認し、DNS変更の確認が完了すれば、ドメインの認証が終了します。
ドメイン認証は、SendGridのDomain Authentication(Authenticate Your Domain)のダイアログに従い、Install DNS Recourdsまで進み、登録するDNS情報を入手して下さい。

取得した追加するDNSレコードを自分のDNSサーバに追加します。
今回は、利用しているVALUE-DOMAIN(バリュードメイン)での設定例を例示して起きます。
ポイントは、DNS設定には時間を要するので、正しく設定出来た状態で、しばらく待ちましょう。数時間で反映されると思いますが、各種サーバなどの設定に依存します。



追加したDNSレコードが確認され、ドメイン認証が完了される際に表示されるWEB画面です。「Verify Your Domain」It worked!と表示されました。

日本法人 公式サイトの関連するQAです。
独自ドメイン利用(Sender Authentication)および設定時のDNSレコードについて、詳しく教えてください

3. SendGridでメール受信時の動作設定(メールをPOSTするWEB APIのアドレスを設定)

ドメイン認証が完了すると SendGridでSendGrid-Inbound Email Parse Webhook(メール受信時とWeb APIのマッピング)設定となります。
SendGridで受信するメールアドレスのドメインおよびWeb APIのURLを設定します。
オプションで、SendGrid側でのSpamチェック有無と受信するPOSTデータを指定します。

4. 自ドメインのSendGridへのメール転送設定(DNS設定)

最後に、SendGridへ転送したいメールアドレスを自分のDNSサーバに設定します。
この設定が終了すると、DNS情報がインターネットに反映され次第、受信メールのSendGridへ転送、SendGridでメール受信時にWEB APIへのメールデータPOSTが開始されます。
必ず、WEB API側の準備後に、DNSレコードの追加を行って下さい。(最低限WEB受信可能な状態)
以下の例では、MXレコードを追加し、対象のサブドメインをSendGridのメールサーバーに転送されるように指定します。

Inbound Email Parse WebhookのPOST情報

HEADERSThe raw headers of the email.
DKIMA string containing the verification results of any DKIM and domain keys signatures in the message.
CONTENT-IDSA string containing the number of attachments.
TOEmail recipient field, as taken from the message headers.
HTMLHTML body of email. If not set, email did not have an HTML body.
FROMEmail sender, as taken from the message headers.
SENDER_IPA string of the sender’s ip address.
SPAM_REPORTSpam Assassin’s spam report.
ENVELOPEA string containing the SMTP envelope. This will have 2 variables: to, which is a single-element array containing the address that we received the email to, and from, which is the return path for the message.
ATTACHMENTSNumber of attachments included in email.
SUBJECTEmail Subject.
SPAM_SCORESpam Assassin’s rating for whether or not this is spam.
ATTACHMENT-INFOA JSON map where the keys are named attachment{X}. Each attachment key points to a JSON object containing three fields, filenametype, and content-id. The filename field is the name of the file (if it was provided). The type field is the media type of the file. X is the total number of attachments. For example, if the number of attachments is 0, there will be no attachment files. If the number of attachments is 3, parameters attachment1, attachment2, and attachment3 will have file uploads.
CHARSETSA string containing the character sets of the fields extracted from the message.
SPFThe results of the Sender Policy Framework verification of the message sender and receiving IP address.

 

[Date] array(16) {
  ["headers"]=>
  string(1970) "Received: by mx0047p1mdw1.sendgrid.net with SMTP id 6WCVv7KAWn Wed, 27 Jul 2016 20:53:06 +0000 (UTC)
Received: from mail-io0-f169.google.com (mail-io0-f169.google.com [209.85.223.169]) by mx0047p1mdw1.sendgrid.net (Postfix) with ESMTPS id AA9FFA817F2 for <example@example.comom>; Wed, 27 Jul 2016 20:53:06 +0000 (UTC)
Received: by mail-io0-f169.google.com with SMTP id b62so81593819iod.3 for <example@example.comom>; Wed, 27 Jul 2016 13:53:06 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sendgrid.com; s=ga1; h=mime-version:from:date:message-id:subject:to; bh=DpB1CYYeumytcPF3q0Upvx3Sq/oF4ZblEwnuVzFwqGI=; b=GH5YTxjt6r4HoTa+94w6ZGQszFQSgegF+Jlv69YV76OLycJI4Gxdwfh6Wlqfez5yID 5dsWuqaVJZQyMq/Dy/c2gHSqVo60BKG56YrynYeSrMPy8abE/6/muPilYxDoPoEyIr/c UXH5rhOKjmJ7nICKu1o99Tfl0cXyCskE7ERW0=
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=DpB1CYYeumytcPF3q0Upvx3Sq/oF4ZblEwnuVzFwqGI=; b=Sq6LVHbmywBdt3sTBn19U8VOmelfoJltz8IcnvcETZsYwk96RBxN+RKMN5fOZSKw4j 15HrgdIFfyDmp67YK0ygvOITlTvZ6XY5I0PtnvDtAQt79kS3tKjI3QKJoEp/ZjIjSzlL KG7agl6cxFgBbIN0yHWBOvy3O+ZXY8tZdom1yOvULjmjW1U9JkdOs+aJ6zq4qhZX/RM/ tIgLB461eJ5V95iQDDc5Ibj9Cvy4vJfXLQRO0nLVQAT2Yz58tkEO1bDZpWOPAyUNneIL yhIWp+SpbuqhMA68mq0krG1PjmWalUbpVcGJIGuOKB9mQFFo/MqdrUCjvYnyo1jPLPeX psdQ==
X-Gm-Message-State: AEkoousvdxmDoxLlTUYJ1AOmCGJv77xRBBlfKv6YrthH0M2NueMwlOxUD6t8nidE9uonXbdJ/DQy/chmHUnN//a4
X-Received: by 10.107.6.101 with SMTP id 98mr38024553iog.41.1469652785829; Wed, 27 Jul 2016 13:53:05 -0700 (PDT)
MIME-Version: 1.0
Received: by 10.107.48.17 with HTTP; Wed, 27 Jul 2016 13:53:05 -0700 (PDT)
From: Sender Name <example@example.com>
Date: Wed, 27 Jul 2016 14:53:05 -0600
Message-ID: <CAN_P_JMvV7ZpAQhOnDienypLrJmuhN=LQWweu4yScw4jQyXY2w@mail.gmail.com>
Subject: Different File Types
To: example@example.comom
Content-Type: multipart/mixed; boundary=001a113f8ad03e85160538a4343c
"
  ["dkim"]=>
  string(22) "{@sendgrid.com : pass}"
  ["content-ids"]=>
  string(37) "{"ii_1562e2169c132d83":"attachment1"}"
  ["to"]=>
  string(26) "example@example.comom"
  ["html"]=>
  string(479) "<div dir="ltr">Here&#39;s an email with multiple attachments<div><br></div><div><img src="cid:ii_1562e2169c132d83" alt="Inline image 1" width="455" height="544"><br clear="all"><div><br></div>-- <br><div class="gmail_signature" data-smartmail="gmail_signature"><div dir="ltr"><img src="https://sendgrid.com/brand/sg-logo-email.png" width="96" height="17"><br><div><br></div></div></div>
</div></div>
"
  ["from"]=>
  string(33) "Sender Name example@example.com"
  ["text"]=>
  string(139) "Here's an email with multiple attachments
"
  ["sender_ip"]=>
  string(14) "209.85.223.169"
  ["spam_report"]=>
  string(844) "Spam detection software, running on the system "mx0047p1mdw1.sendgrid.net", has
identified this incoming email as possible spam. The original message
has been attached to this so you can view it (if it isn't spam) or label
similar future email. If you have any questions, see
@@CONTACT_ADDRESS@@ for details.

Content preview:  Here's an email with multiple attachments [image: Inline image
   1] -- [...]

Content analysis details:   (2.6 points, 5.0 required)

 pts rule name              description
---- ---------------------- --------------------------------------------------
 0.8 HTML_IMAGE_RATIO_02    BODY: HTML has a low ratio of text to image area
 0.0 HTML_MESSAGE           BODY: HTML included in message
 1.8 HTML_IMAGE_ONLY_08     BODY: HTML: images with 400-800 bytes of words
 0.0 T_MIME_NO_TEXT         No text body parts

"
  ["envelope"]=>
  string(66) "{"to":["example@example.comom"],"from":"example@example.com"}"
  ["attachments"]=>
  string(1) "2"
  ["subject"]=>
  string(20) "Different File Types"
  ["spam_score"]=>
  string(5) "2.597"
  ["attachment-info"]=>
  string(287) "{"attachment2":{"filename":"DockMcWordface.docx","name":"DockMcWordface.docx","type":"application/vnd.openxmlformats-officedocument.wordprocessingml.document"},"attachment1":{"filename":"MG_2359.jpg","name":"_MG_2359.jpg","type":"image/jpeg","content-id":"ii_1562e2169c132d83"}}"
  ["charsets"]=>
  string(77) "{"to":"UTF-8","html":"UTF-8","subject":"UTF-8","from":"UTF-8","text":"UTF-8"}"
  ["SPF"]=>
  string(4) "pass"
}



SendGridのInbound Email Parseを利用

SendGridは、メール配信などで利用されている方は多いのかもしれませんが、メール受信トリガーにWEB API経由でPOSTしてくれるサービスも提供されているようです。メール受信時のWEB APIプッシュの実現方法を探している際に見つけました。
グローバルで、企業向けにサービスを展開されているので、サービス品質などに間違いはなさそうです。
IFTTTでメール受信をしっかりとトリガー出来ていれば本サービスを利用することも無かったかもしれませんが、利用してみてPubSubというような使い方が出来そうなのでメインで利用する予定です。一般向け製品では通知手段がメールというサービスが多いです。

SendGrid | メールを成功の原動力に。

前提条件

  • PHPが動作するWEBサーバがセットアップされている
  • WEBサーバの公開設定が終わっている

SendGridのInbound Email Parse向けPHP APIサンプル

WEBサーバーにPOST受信可能なPHPファイルを配置し、SendGridがメール受信したさいのWEBリクエストを受信するPHP APIを準備します。

以下は、サンプルとなります。受信後に実施したい処理を利用形態に合わせて実装して下さい。本ページの各種ユースケースもサンプルになると思います。ユースケース一覧 V1

  • メール受信時にDBにレコード登録(モーションセンサ感知時にメール通知用)
  • ホームセキュリティ通知をDBにレコード登録(活動ログとして)
  • 各種速報メール受信時のLINE通知など

以下サンプルコードとなります。

<?php
/*************************************************************************
 * sendgrid-api (MAIN)
 * Home Tools for private. Using IFTTT and Google Home etc
 *
 * PHP 5 or later
 *
 * @category  Home IoT
 * @author    Miki
 * @url       https://www.miki-ie.com/
 * @copyright 2019 (c) MIKI-IE All rights Reserved.
 * @license   https://opensource.org/licenses/mit-license.html MIT License
 * @version   1.0
*************************************************************************/
//ログのファイル名
define("SENDGRID_API_LOG_NAME","sendgrid");
//DB
define("DB_HOST","@IP_Adress@:@Port@");
define("DB_USER","@User@");
define("DB_PASS","@Password@");
define("DB_DBNAME","@DB_Name@");
define("DB_TABLENAME1","@TableName1@");
define("DB_TABLENAME2","@TableName2@");


function addDBRecord($table, $datetime, $value1) {
    $mysqli = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_DBNAME);

    if (mysqli_connect_errno()) {
        logger("Connect failed: ".mysqli_connect_error(),"ERROR");
        exit();
	}
	
	if (!$mysqli->set_charset("utf8")) {
        logger("Error loading character set utf8: ".$mysqli->error,"ERROR");
		exit();
	}

	switch ($table) {
		case DB_TABLENAME1:
			// SQL(INSERT)を作成
			$sql = "INSERT INTO $table (
				DATETIME, COUNT
				) VALUES (
				'$datetime', $value1
				)";
			break;
		//addDBRecord(DB_TABLENAME2, $datetime_text, $state_text);
		case DB_TABLENAME2:
		// SQL(INSERT)を作成
		$str = mb_convert_encoding($value1, "UTF-8");
		$sql = "INSERT INTO $table (
			DATETIME2, STATE
			) VALUES (
				'$datetime', N'$str'
			)";
		break;
		default:
			logger("Internal DB Tabel Name Error. table:{$table}","ERROR");
	}

	if (!$mysqli->query($sql)) {
        logger("SQL query error Errormessage: ".$mysqli->error,"ERROR");
	}

	$mysqli->close();
}

function logger($text, $level) {
	$datetime = date('Y-m-d H:i:s');
	$date = date('Ym');
	$file_name = __DIR__ . "/log/".SENDGRID_API_LOG_NAME."-{$date}.log";
	$text = "{$datetime} [{$level}] {$text}" . PHP_EOL;
	echo $text;
	if(!(file_exists($file_name))){
		touch($file_name);
		chmod($file_name, 0777);
	}
	return error_log(print_r($text, TRUE), 3, $file_name);
}

$post_from = $_POST['from'];
$post_charsets = $_POST['charsets'];
$post_charsets_array = json_decode($post_charsets, true);
$post_charsets_all = print_r($post_charsets_array, true);
$post_encode_subject = mb_convert_encoding($_POST['subject'], "UTF-8", $post_charsets_array["subject"]);
$post_encode_subject = preg_replace('/[\x00-\x1F\x7F]/', '', $post_encode_subject);
$post_encode_text = mb_convert_encoding($_POST['text'], "UTF-8", $post_charsets_array["text"]);
$post_encode_text = preg_replace('/[\x00-\x1F\x7F]/', '', $post_encode_text);
logger("mb_convert_encoding, post_subject : {$post_encode_subject}","DEBUG");
logger("mb_convert_encoding, post_text : {$post_encode_text}","DEBUG");

$text_substr = mb_substr($post_text, 0, 15);
logger("Start sendgrid: key={$key} , from={$post_from} , subject={$post_subject} , text={$text_substr}","INFO");

if(isset($_POST['from'])) {
	switch ($post_from) {
	case 'aaa1@bbb.ccc': //メール受信内容に合わせて個別処理。以下はメール本文から文字列切り出しを実施
		$start = mb_strpos($post_text, "時刻:");
		$datetime_text = mb_substr($post_text, $start + 4, 19,"utf-8");
		logger("Start CAM-A7DE datetime : {$datetime_text}","INFO");
		addDBRecord(DB_TABLENAME1, $datetime_text, 1);
		break;
	case 'aaa2@bbb.ccc': //メール受信内容に合わせて個別処理。以下はメール本文から文字列切り出しを実施
		$datetime_text = mb_substr($post_text, 0, 16,"utf-8");
		$temp_text = mb_substr($post_text, 18);
		$end_point = mb_strpos($temp_text, "。");
		$state_text = mb_substr($temp_text, 0, $end_point,"utf-8");
		logger("end_point : {$end_point} ,state : {$state_text}","DEBUG");
		logger("Start CSP-Security datetime : {$datetime_text} state : {$state_text}","INFO");
		addDBRecord(DB_TABLENAME2, $datetime_text, $state_text);
		break;
	default:
		logger("This is private API. (in Default)","ERROR");
	}
}else{
	logger("This is private API. (in else)","ERROR");
}

?>

Inbound Email Parse WebhookのPOST情報

HEADERSThe raw headers of the email.
DKIMA string containing the verification results of any DKIM and domain keys signatures in the message.
CONTENT-IDSA string containing the number of attachments.
TOEmail recipient field, as taken from the message headers.
HTMLHTML body of email. If not set, email did not have an HTML body.
FROMEmail sender, as taken from the message headers.
SENDER_IPA string of the sender’s ip address.
SPAM_REPORTSpam Assassin’s spam report.
ENVELOPEA string containing the SMTP envelope. This will have 2 variables: to, which is a single-element array containing the address that we received the email to, and from, which is the return path for the message.
ATTACHMENTSNumber of attachments included in email.
SUBJECTEmail Subject.
SPAM_SCORESpam Assassin’s rating for whether or not this is spam.
ATTACHMENT-INFOA JSON map where the keys are named attachment{X}. Each attachment key points to a JSON object containing three fields, filenametype, and content-id. The filename field is the name of the file (if it was provided). The type field is the media type of the file. X is the total number of attachments. For example, if the number of attachments is 0, there will be no attachment files. If the number of attachments is 3, parameters attachment1, attachment2, and attachment3 will have file uploads.
CHARSETSA string containing the character sets of the fields extracted from the message.
SPFThe results of the Sender Policy Framework verification of the message sender and receiving IP address.

 

[Date] array(16) {
  ["headers"]=>
  string(1970) "Received: by mx0047p1mdw1.sendgrid.net with SMTP id 6WCVv7KAWn Wed, 27 Jul 2016 20:53:06 +0000 (UTC)
Received: from mail-io0-f169.google.com (mail-io0-f169.google.com [209.85.223.169]) by mx0047p1mdw1.sendgrid.net (Postfix) with ESMTPS id AA9FFA817F2 for <example@example.comom>; Wed, 27 Jul 2016 20:53:06 +0000 (UTC)
Received: by mail-io0-f169.google.com with SMTP id b62so81593819iod.3 for <example@example.comom>; Wed, 27 Jul 2016 13:53:06 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sendgrid.com; s=ga1; h=mime-version:from:date:message-id:subject:to; bh=DpB1CYYeumytcPF3q0Upvx3Sq/oF4ZblEwnuVzFwqGI=; b=GH5YTxjt6r4HoTa+94w6ZGQszFQSgegF+Jlv69YV76OLycJI4Gxdwfh6Wlqfez5yID 5dsWuqaVJZQyMq/Dy/c2gHSqVo60BKG56YrynYeSrMPy8abE/6/muPilYxDoPoEyIr/c UXH5rhOKjmJ7nICKu1o99Tfl0cXyCskE7ERW0=
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=DpB1CYYeumytcPF3q0Upvx3Sq/oF4ZblEwnuVzFwqGI=; b=Sq6LVHbmywBdt3sTBn19U8VOmelfoJltz8IcnvcETZsYwk96RBxN+RKMN5fOZSKw4j 15HrgdIFfyDmp67YK0ygvOITlTvZ6XY5I0PtnvDtAQt79kS3tKjI3QKJoEp/ZjIjSzlL KG7agl6cxFgBbIN0yHWBOvy3O+ZXY8tZdom1yOvULjmjW1U9JkdOs+aJ6zq4qhZX/RM/ tIgLB461eJ5V95iQDDc5Ibj9Cvy4vJfXLQRO0nLVQAT2Yz58tkEO1bDZpWOPAyUNneIL yhIWp+SpbuqhMA68mq0krG1PjmWalUbpVcGJIGuOKB9mQFFo/MqdrUCjvYnyo1jPLPeX psdQ==
X-Gm-Message-State: AEkoousvdxmDoxLlTUYJ1AOmCGJv77xRBBlfKv6YrthH0M2NueMwlOxUD6t8nidE9uonXbdJ/DQy/chmHUnN//a4
X-Received: by 10.107.6.101 with SMTP id 98mr38024553iog.41.1469652785829; Wed, 27 Jul 2016 13:53:05 -0700 (PDT)
MIME-Version: 1.0
Received: by 10.107.48.17 with HTTP; Wed, 27 Jul 2016 13:53:05 -0700 (PDT)
From: Sender Name <example@example.com>
Date: Wed, 27 Jul 2016 14:53:05 -0600
Message-ID: <CAN_P_JMvV7ZpAQhOnDienypLrJmuhN=LQWweu4yScw4jQyXY2w@mail.gmail.com>
Subject: Different File Types
To: example@example.comom
Content-Type: multipart/mixed; boundary=001a113f8ad03e85160538a4343c
"
  ["dkim"]=>
  string(22) "{@sendgrid.com : pass}"
  ["content-ids"]=>
  string(37) "{"ii_1562e2169c132d83":"attachment1"}"
  ["to"]=>
  string(26) "example@example.comom"
  ["html"]=>
  string(479) "<div dir="ltr">Here&#39;s an email with multiple attachments<div><br></div><div><img src="cid:ii_1562e2169c132d83" alt="Inline image 1" width="455" height="544"><br clear="all"><div><br></div>-- <br><div class="gmail_signature" data-smartmail="gmail_signature"><div dir="ltr"><img src="https://sendgrid.com/brand/sg-logo-email.png" width="96" height="17"><br><div><br></div></div></div>
</div></div>
"
  ["from"]=>
  string(33) "Sender Name example@example.com"
  ["text"]=>
  string(139) "Here's an email with multiple attachments
"
  ["sender_ip"]=>
  string(14) "209.85.223.169"
  ["spam_report"]=>
  string(844) "Spam detection software, running on the system "mx0047p1mdw1.sendgrid.net", has
identified this incoming email as possible spam. The original message
has been attached to this so you can view it (if it isn't spam) or label
similar future email. If you have any questions, see
@@CONTACT_ADDRESS@@ for details.

Content preview:  Here's an email with multiple attachments [image: Inline image
   1] -- [...]

Content analysis details:   (2.6 points, 5.0 required)

 pts rule name              description
---- ---------------------- --------------------------------------------------
 0.8 HTML_IMAGE_RATIO_02    BODY: HTML has a low ratio of text to image area
 0.0 HTML_MESSAGE           BODY: HTML included in message
 1.8 HTML_IMAGE_ONLY_08     BODY: HTML: images with 400-800 bytes of words
 0.0 T_MIME_NO_TEXT         No text body parts

"
  ["envelope"]=>
  string(66) "{"to":["example@example.comom"],"from":"example@example.com"}"
  ["attachments"]=>
  string(1) "2"
  ["subject"]=>
  string(20) "Different File Types"
  ["spam_score"]=>
  string(5) "2.597"
  ["attachment-info"]=>
  string(287) "{"attachment2":{"filename":"DockMcWordface.docx","name":"DockMcWordface.docx","type":"application/vnd.openxmlformats-officedocument.wordprocessingml.document"},"attachment1":{"filename":"MG_2359.jpg","name":"_MG_2359.jpg","type":"image/jpeg","content-id":"ii_1562e2169c132d83"}}"
  ["charsets"]=>
  string(77) "{"to":"UTF-8","html":"UTF-8","subject":"UTF-8","from":"UTF-8","text":"UTF-8"}"
  ["SPF"]=>
  string(4) "pass"
}



Seleniumでブラウザ自動操作(スクレイピング)

Raspberry Piでない環境で利用したことのあるSleniumの導入を行いました。
各種WEBツールの自動操作(スクレイピング)などが出来ると、更に自宅IoTの幅が広がると思い、導入してみましたが、「Raspberry Pi 3 Model B+」のCPUが1.2GHz、4コアのBroadcom BCM2837から1.4GHz駆動4コアのBCM2837B0は、32bitでありchromedriverの導入方法が限られ、高い信頼性で動作する状態まで構築出来ておりません。とりあえず動作しますが、信頼性が低いです。(数回に1回は失敗します)

pi@raspberrypi:~ $ cat /proc/cpuinfo
processor	: 0
model name	: ARMv7 Processor rev 4 (v7l)
BogoMIPS	: 38.40
Features	: half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
CPU implementer	: 0x41
CPU architecture: 7
CPU variant	: 0x0
CPU part	: 0xd03
CPU revision	: 4

processor	: 1
model name	: ARMv7 Processor rev 4 (v7l)
BogoMIPS	: 38.40
Features	: half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
CPU implementer	: 0x41
CPU architecture: 7
CPU variant	: 0x0
CPU part	: 0xd03
CPU revision	: 4

processor	: 2
model name	: ARMv7 Processor rev 4 (v7l)
BogoMIPS	: 38.40
Features	: half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
CPU implementer	: 0x41
CPU architecture: 7
CPU variant	: 0x0
CPU part	: 0xd03
CPU revision	: 4

processor	: 3
model name	: ARMv7 Processor rev 4 (v7l)
BogoMIPS	: 38.40
Features	: half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
CPU implementer	: 0x41
CPU architecture: 7
CPU variant	: 0x0
CPU part	: 0xd03
CPU revision	: 4

Hardware	: BCM2835
Revision	: a020d3
Serial		: 00000000e63ad038
pi@raspberrypi:~ $ cat /etc/debian_version
10.0
pi@raspberrypi:~ $ uname -a
Linux raspberrypi 4.19.57-v7+ #1244 SMP Thu Jul 4 18:45:25 BST 2019 armv7l GNU/Linux

Pipでseleniumを導入

root@raspberrypi:~# pip install selenium
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting selenium
Downloading https://files.pythonhosted.org/packages/80/d6/4294f0b4bce4de0abf13e17190289f9d0613b0a44e5dd6a7f5ca98459853/selenium-3.141.0-py2.py3-none-any.whl (904kB)
100% |████████████████████████████████| 911kB 306kB/s
Requirement already satisfied: urllib3 in /usr/lib/python2.7/dist-packages (from selenium) (1.24.1)
Installing collected packages: selenium
Successfully installed selenium-3.141.0
@raspberrypi:~ $ pip list |grep selenium
selenium 3.141.0

chromium-browser導入

sudo apt-get install chromium-browser

Chromedirever導入

  1. オフィシャルサイトよりダウンロード(一番推奨される方法)
    ただし、Linux版は64Bitしか提供されていない。https://sites.google.com/a/chromium.org/chromedriver/downloads
    よって、https://launchpad.net/ubuntu/trusty/+package/chromium-chromedriverよりダウンロードしたドライバを利用します。
    参考URL:Raspberry Piによるスクレイピング 環境構築(Selenium + Chromiumドライバ)
  2. apt sourceを利用したインストール
    参考URL:Raspberry pi3上でChromeのheadlessモードによる小型低電力&自動化環境への第一歩
  3. pipでのインストール
    参考URL:【Python】SeleniumでHeadless Chromeを使おう
  4. npmでのインストール
    sudo npm -g install chromedriver

2. apt sourceでのインストール

chromiumブラウザともバージョンが合い、まだ安定して動作しました。

sudo apt-get install chromium-chromedriver

3. pipでのインストール

あまり安定して動作しませんでした。

root@raspberrypi:~# pip install chromedriver
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting chromedriver
Downloading https://files.pythonhosted.org/packages/c8/5e/1daf3c71852f5b8536e30b2afa1e6442c454e91947678ac1a37daba2d7f5/chromedriver-2.24.1-py2.py3-none-any.whl (14.3MB)
100% |████████████████████████████████| 14.3MB 16kB/s
Installing collected packages: chromedriver
Successfully installed chromedriver-2.24.1
miki@raspberrypi:~ $ pip list |grep chromedriver
chromedriver 2.24.1

Selenium Webdriverの動作確認

以下のスクリプトでheadlessモードでの動作検証を行いました。タイミングにより失敗するケースも有り、IoT家電のメインパスとして利用するにはもう少し試行錯誤が必要かなという状況です。ただし、APIがなくWEBのみに温度など取得したい情報があるツールなどの情報GET系には利用していこうかなと考えております。
それとは、別に設定変更など操作を行うことはもう少し、信頼のおける構築方法が見つかってから導入したいと考えております。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

CHROME_BIN = "/usr/bin/chromium-browser"
CHROME_DRIVER = '/usr/lib/chromium-browser/chromedriver'

options = Options()
options.binary_location = CHROME_BIN
options.add_argument('--headless')

driver = webdriver.Chrome(CHROME_DRIVER, options=options)

driver.get("https://www.google.co.jp")

time.sleep(5)

html = driver.page_source
print(html)
driver.quit()

Python3とPip導入

SynologyのパッケージセンターよりPython3を導入することが出来ます。

Python3をインストール

Pipをインストール

以下、Pipのコマンドラインインストールです。必要ファイルをダウンロードし、そのままインストールを実行します。

@Synology:~$ sudo curl -k https://bootstrap.pypa.io/get-pip.py | sudo python3
Password:
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 1669k  100 1669k    0     0  2403k      0 --:--:-- --:--:-- --:--:-- 2405k
Collecting pip
  Downloading https://files.pythonhosted.org/packages/5c/e0/be401c003291b56efc55aeba6a80ab790d3d4cece2778288d65323009420/pip-19.1.1-py2.py3-none-any.whl (1.4MB)
     |████████████████████████████████| 1.4MB 1.3MB/s
Collecting setuptools
  Downloading https://files.pythonhosted.org/packages/ec/51/f45cea425fd5cb0b0380f5b0f048ebc1da5b417e48d304838c02d6288a1e/setuptools-41.0.1-py2.py3-none-any.whl (575kB)
     |████████████████████████████████| 583kB 7.2MB/s
Collecting wheel
  Downloading https://files.pythonhosted.org/packages/bb/10/44230dd6bf3563b8f227dbf344c908d412ad2ff48066476672f3a72e174e/wheel-0.33.4-py2.py3-none-any.whl
Installing collected packages: pip, setuptools, wheel
  WARNING: The script wheel is installed in '/volume1/@appstore/py3k/usr/local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed pip-19.1.1 setuptools-41.0.1 wheel-0.33.4

インストール後に、PATH指定と導入したpipのバージョン確認を実施。

@Synology:~$ export PATH="$PATH:/volume1/@appstore/py3k/usr/local/bin"
@Synology:~$ pip -V
pip 19.1.1 from /volume1/@appstore/py3k/usr/local/lib/python3.5/site-packages/pip (python 3.5)

Pipの動作確認も兼ねて、Seleniumをインストール

@Synology:~$ sudo pip install selenium
Collecting selenium
Downloading https://files.pythonhosted.org/packages/80/d6/4294f0b4bce4de0abf13e17190289f9d0613b0a44e5dd6a7f5ca98459853/selenium-3.141.0-py2.py3-none-any.whl (904kB)
|████████████████████████████████| 911kB 1.2MB/s
Collecting urllib3 (from selenium)
Downloading https://files.pythonhosted.org/packages/e6/60/247f23a7121ae632d62811ba7f273d0e58972d75e58a94d329d51550a47d/urllib3-1.25.3-py2.py3-none-any.whl (150kB)
|████████████████████████████████| 153kB 6.3MB/s
Installing collected packages: urllib3, selenium
Successfully installed selenium-3.141.0 urllib3-1.25.3

参考にさせて頂いたサイト

SynologyでPython! pipが使えるようにする

SynologyにWEBカメラ導入

Raspberry PiにWEBカメラを接続しました。
設置場所の自由度と、設置時のスマートさを優先させて、Raspberry Pi標準のカメラモジュールでなく、市販されているWEBカメラを利用することにしました。

UVC(USB Video Class)に対応した製品が、Raspberry認識時にトラブルも少ないと思います。UVCとは、USB Implementers Forumにより定義されたUSBのデバイスクラスとなります。

BUFFALO 200万画素WEBカメラ 広角120°マイク内蔵 ブラック BSW200MBK

価格comやアマゾンでのランキングも高く、120度の広角など購入時点だと、同価格帯の類似製品よりは魅力的な製品でした。

カメラ接続とguvcviewインストール

接続後には、Bus001に接続したカメラがUSBデバイスとして認識されました。

@raspberrypi:~ $ lsusb
Bus 001 Device 004: ID 0458:708c KYE Systems Corp. (Mouse Systems) Genius WideCam F100
Bus 001 Device 005: ID 0424:7800 Standard Microsystems Corp.
Bus 001 Device 003: ID 0424:2514 Standard Microsystems Corp. USB 2.0 Hub
Bus 001 Device 002: ID 0424:2514 Standard Microsystems Corp. USB 2.0 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

続いて、「guvcview」をインストールします。

@raspberrypi:~ $ sudo apt-get install
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
以下のパッケージが自動でインストールされましたが、もう必要とされていません:
libboost-system1.62.0 libboost-thread1.62.0 libreoffice-gtk2
これを削除するには 'sudo apt autoremove' を利用してください。
以下の追加パッケージがインストールされます:
libgsl23 libgslcblas0 libguvcview-2.0-2 libportaudio2 libwebcam0 uvcdynctrl
uvcdynctrl-data
提案パッケージ:
gsl-ref-psdoc | gsl-doc-pdf | gsl-doc-info | gsl-ref-html
以下のパッケージが新たにインストールされます:
guvcview libgsl23 libgslcblas0 libguvcview-2.0-2 libportaudio2 libwebcam0
uvcdynctrl uvcdynctrl-data
アップグレード: 0 個、新規インストール: 8 個、削除: 0 個、保留: 0 個。
1,132 kB のアーカイブを取得する必要があります。
この操作後に追加で 3,663 kB のディスク容量が消費されます。
続行しますか? [Y/n] y
取得:1 http://ftp.jaist.ac.jp/pub/Linux/raspbian-archive/raspbian buster/main armhf libgslcblas0 armhf 2.5+dfsg-6 [79.0 kB]
取得:2 http://ftp.jaist.ac.jp/pub/Linux/raspbian-archive/raspbian buster/main armhf libgsl23 armhf 2.5+dfsg-6 [704 kB]
取得:3 http://ftp.jaist.ac.jp/pub/Linux/raspbian-archive/raspbian buster/main armhf libportaudio2 armhf 19.6.0-1 [56.9 kB]
取得:4 http://ftp.jaist.ac.jp/pub/Linux/raspbian-archive/raspbian buster/main armhf libguvcview-2.0-2 armhf 2.0.6+debian-1 [107 kB]
取得:5 http://ftp.jaist.ac.jp/pub/Linux/raspbian-archive/raspbian buster/main armhf guvcview armhf 2.0.6+debian-1 [137 kB]
取得:6 http://ftp.jaist.ac.jp/pub/Linux/raspbian-archive/raspbian buster/main armhf libwebcam0 armhf 0.2.4-1.1 [22.1 kB]
取得:7 http://ftp.jaist.ac.jp/pub/Linux/raspbian-archive/raspbian buster/main armhf uvcdynctrl-data all 0.2.4-1.1 [9,916 B]
取得:8 http://ftp.jaist.ac.jp/pub/Linux/raspbian-archive/raspbian buster/main armhf uvcdynctrl armhf 0.2.4-1.1 [16.4 kB]
1,132 kB を 5秒 で取得しました (221 kB/s)
以前に未選択のパッケージ libgslcblas0:armhf を選択しています。
(データベースを読み込んでいます ... 現在 151093 個のファイルとディレクトリがインストールされています。)
.../0-libgslcblas0_2.5+dfsg-6_armhf.deb を展開する準備をしています ...
libgslcblas0:armhf (2.5+dfsg-6) を展開しています...
以前に未選択のパッケージ libgsl23:armhf を選択しています。
.../1-libgsl23_2.5+dfsg-6_armhf.deb を展開する準備をしています ...
libgsl23:armhf (2.5+dfsg-6) を展開しています...
以前に未選択のパッケージ libportaudio2:armhf を選択しています。
.../2-libportaudio2_19.6.0-1_armhf.deb を展開する準備をしています ...
libportaudio2:armhf (19.6.0-1) を展開しています...
以前に未選択のパッケージ libguvcview-2.0-2:armhf を選択しています。
.../3-libguvcview-2.0-2_2.0.6+debian-1_armhf.deb を展開する準備をしています ...
libguvcview-2.0-2:armhf (2.0.6+debian-1) を展開しています...
以前に未選択のパッケージ guvcview を選択しています。
.../4-guvcview_2.0.6+debian-1_armhf.deb を展開する準備をしています ...
guvcview (2.0.6+debian-1) を展開しています...
以前に未選択のパッケージ libwebcam0 を選択しています。
.../5-libwebcam0_0.2.4-1.1_armhf.deb を展開する準備をしています ...
libwebcam0 (0.2.4-1.1) を展開しています...
以前に未選択のパッケージ uvcdynctrl-data を選択しています。
.../6-uvcdynctrl-data_0.2.4-1.1_all.deb を展開する準備をしています ...
uvcdynctrl-data (0.2.4-1.1) を展開しています...
以前に未選択のパッケージ uvcdynctrl を選択しています。
.../7-uvcdynctrl_0.2.4-1.1_armhf.deb を展開する準備をしています ...
uvcdynctrl (0.2.4-1.1) を展開しています...
libportaudio2:armhf (19.6.0-1) を設定しています ...
libgslcblas0:armhf (2.5+dfsg-6) を設定しています ...
uvcdynctrl-data (0.2.4-1.1) を設定しています ...
libgsl23:armhf (2.5+dfsg-6) を設定しています ...
libwebcam0 (0.2.4-1.1) を設定しています ...
libguvcview-2.0-2:armhf (2.0.6+debian-1) を設定しています ...
uvcdynctrl (0.2.4-1.1) を設定しています ...
guvcview (2.0.6+debian-1) を設定しています ...
mime-support (3.62) のトリガを処理しています ...
gnome-menus (3.31.4-3) のトリガを処理しています ...
libc-bin (2.28-10+rpi1) のトリガを処理しています ...
man-db (2.8.5-2) のトリガを処理しています ...
desktop-file-utils (0.23-4) のトリガを処理しています ...

guvcviewを起動

コマンドライン、またはGUIよりguvcviewを起動します。

コマンドラインからのguvcview起動

@raspberrypi:~ $ guvcview &
[1] 2258

GUIからのguvcview起動

起動後のサンプル画面

参考にさせて頂いたサイト

RaspberryPiでUSB接続のWebカメラを使ってみる

VNCサーバ導入

基本的にコマンドラインで操作していたRaspberry Piですが、WEBカメラの導入や開発環境の利用に先立ち、VNCサーバを導入しました。
VNC ViewerをWindowsなどのクライアントにインストールし、Raspberry PiのVNCサーバの有効化を行います。
さらに、Raspberry Piに「xrdp」「remmina」をインストールしました。

VNC Viewerのインストール

以下のURLよりVNC Viewerをダウンロードし、クライアントにインストールします。

VNC® Connect consists of VNC® Viewer and VNC® Server

Raspberry PiのVNCサーバを有効化

Raspberry Pi の「raspi-config」コマンドで、「Raspberry Pi Software Configuration Tool (raspi - config)」を開きます。画面には、Raspberry Pi 3 Model B Plus Rev 1.3が表示されています。

sudo raspi-config

「5 Interfacing Options Configure connections to peripherals」を選択します。

「P3 VNC Enable/Disable graphical remote access to your Pi using RealVNC」を選択し、VNCサーバをEnable(有効化)します。

「xrdp」および「remmina」導入

@raspberrypi: $ sudo apt-get install xrdp
[sudo] password for:
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
libboost-system1.62.0 libboost-thread1.62.0
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
libglu1-mesa x11-apps x11-session-utils xbitmaps xfonts-75dpi xfonts-base
xfonts-scalable xorg xorg-docs-core xorgxrdp
Suggested packages:
mesa-utils xorg-docs x11-xfs-utils guacamole xrdp-pulseaudio-installer
The following NEW packages will be installed:
libglu1-mesa x11-apps x11-session-utils xbitmaps xfonts-75dpi xfonts-base
xfonts-scalable xorg xorg-docs-core xorgxrdp xrdp
0 upgraded, 11 newly installed, 0 to remove and 1 not upgraded.
Need to get 11.0 MB of archives.
After this operation, 17.8 MB of additional disk space will be used.
Do you want to continue? [Y/n]
@raspberrypi: $ sudo apt-get install remmina
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
libboost-system1.62.0 libboost-thread1.62.0
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
libavahi-ui-gtk3-0 libayatana-appindicator3-1 libayatana-ido3-0.4-0
libayatana-indicator3-7 libdbusmenu-glib4 libdbusmenu-gtk3-4
libfreerdp-client2-2 libfreerdp2-2 libssh-4 libvncclient1 libwinpr2-2
remmina-common remmina-plugin-rdp remmina-plugin-secret remmina-plugin-vnc
Suggested packages:
freerdp2-x11 remmina-plugin-exec remmina-plugin-nx remmina-plugin-spice
remmina-plugin-telepathy remmina-plugin-xdmcp
The following NEW packages will be installed:
libavahi-ui-gtk3-0 libayatana-appindicator3-1 libayatana-ido3-0.4-0
libayatana-indicator3-7 libdbusmenu-glib4 libdbusmenu-gtk3-4
libfreerdp-client2-2 libfreerdp2-2 libssh-4 libvncclient1 libwinpr2-2
remmina remmina-common remmina-plugin-rdp remmina-plugin-secret
remmina-plugin-vnc
0 upgraded, 16 newly installed, 0 to remove and 1 not upgraded.
Need to get 3274 kB of archives.
After this operation, 8144 kB of additional disk space will be used.
Do you want to continue? [Y/n]

トラブルシュート(Cannot currently show the desktop)

インストール時のブートオプションで、Text Consoleを選択していたのが原因で、VNCクライントからアクセスした際に「Cannot currently show the desktop」と表示され、VNC経由でのRaspberry Piにアクセスが出来ませんでした。

  1. 「raspi-config」でRaspberry Pi Software Configuration Tool(raspi - config)を起動します
  2. 「3 Boot Options Configure options for start-up」を選択します
  3. 「B1 Desktop /CLI Choose whether to boot into a desktop enviroment or the command line」を選択します
  4. 「B3 Desktop Desktop GUI, requiring user to login」を選択肢、GUIモードで起動するように変更します。

メールサーバー(Mail Plus)でSMTP構築

25番ポートブロック(Outbound Port25 Blocking)が設定されていたり、GmailのSMTP利用基準が厳しくなったりと、何かとIoT家電などからメール送信する際に独自のメール送信SMTPサーバが利用したい場面が出てきます。今回は、SynologyにSMTPメール送信サーバを構築します。Synologyの通知機能で用いるメールサーバも今回構築するSMTPメールサーバ経由に設定します。
また、迷惑メール防止で、自宅サーバのメール送信もIP逆引きが確認されたりと厳しくなっているので、Synologyでリレーメール設定(SMTPリレー)を行い、利用しているプロバイダのSMTPサーバ経由(サブミッションポート:Port 587、SMTP-Auth)でメールを設定します。
ちなみに、メールのメインの受信サーバおよびIMAP/POPサーバは、サブドメイン無制限のレンタルサーバを運用している環境があるので、MXレコードを利用している外部のサーバにしております。自宅のネットワーク環境+Synologyでメールサーバを本番運用するまでには至っておりません。
外部サーバがない方も、個人的には、メール転送を利用して、Gmailに転送し、Gmailから独自ドメインのメールも送信出来るように設定し運用することをおすすめします。
[ITmedia]Gmailで差出人を別のメールアドレスに変更して送信する

メールサーバ(Mail Plus)をインストール

「パッケージセンター」よりMail Serverをインストールします。

SMTPサーバを設定

メールサーバーの設定画面を開きます

メールサーバーインストール後、設定画面を開きます。TLS / SSLプロファイルレベルの警告が出ます。

SMTPの設定画面を開き、SMTPサーバ設定を入力します

SMTPを有効にするにチェックを入れます。

SMTP認証を有効にするにチェックを入れ、LAN接続の認証を無視するにチェックを入れます(利用する環境に合わせて設定下さい)。
ホスト名(FQDN)に、利用するメールのドメイン名を入力します。
ローカルのみで利用するので、ポートは、SMTP標準の25を設定します。

SMTPリレー設定を行います

SMTP設定画面より「SMTPリレー」設定画面を開きます。
SMTPリレー設定画面では、プロバイダのSMTPサーバ、ポートとアカウント情報を入力して、SMTPリレー設定を行います。

Synologyの通知設定のメールサーバを変更

「コントロールパネル」「通知」を開きます

「コントロールパネル」「通知」の「電子メール」タブを設定します

電子メール通知を有効にするにチェックを入れます。
通知先メールアドレスを受信者のEメールアドレスに設定します。
SMTPサーバに「localhost」を設定し、SMTPポートに「25」を設定します。

送信者の電子メールに、SMTPサーバで設定したFQDNで利用可能なメールアドレスをしてします。

すべて設定後、「テストメールの送信」でテストメールが受信者のEメールアドレスで設定したアドレスで受信できるか確認して下さい。

 

Raspberry Piのバックアップ

SDカードのみの運用、他のコンポーネントの信頼性も不明瞭なので、とりあえずまるごとSynologyへバックアップを行い、故障などが起きた際に備えます。

Synologyでrsync利用設定

「コントロールパネル」「ファイルサービス」「rsync」タブの「rsyncサービスを有効にする」にチェックを入れます。。設定変更時には「適用」ボタンを押して設定を有効化する必要があります。

Raspberry Piのバックアップ設定(Rsync)

バックアップ対象

現在のボリューム構成は、以下となります。(「df -h」コマンド結果)
/ ルートフォルダ全体をバックアップ対象とし、バックアップから除外するファイルリストをファイルで準備します。「/rsync-exclude.txt」として準備しました。

@raspberrypi:~# df -h
ファイルシス サイズ 使用 残り 使用% マウント位置
/dev/root 116G 5.8G 105G 6% /
devtmpfs 459M 0 459M 0% /dev
tmpfs 464M 0 464M 0% /dev/shm
tmpfs 464M 14M 450M 3% /run
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 464M 0 464M 0% /sys/fs/cgroup
/dev/mmcblk0p6 253M 40M 213M 16% /boot
tmpfs 93M 0 93M 0% /run/user/1000
tmpfs 93M 0 93M 0% /run/user/1001
@raspberrypi:~# sudo vi /rsync-exclude.txt
@raspberrypi:~#
@raspberrypi:~# sudo cat /rsync-exclude.txt
/proc/*
/sys/*
/dev/*
/boot/*
/tmp/*
/run/*
/mnt/*
/var/tmp/*
/var/log/*
/media/*

rsyncコマンド

今回は、以下のコマンドを利用することにしました。

バックアップ先 /mnt/synology/raspberry_backup (フォルダは事前に作成して置きます)

sudo rsync -aEv --delete-during --exclude-from=/rsync-exclude.txt / /mnt/synology/raspberry_backup/ --log-file=/home/rsync.log
rsync  version 3.1.3  protocol version 31
Copyright (C) 1996-2018 by Andrew Tridgell, Wayne Davison, and others.
Web site: http://rsync.samba.org/
Capabilities:
    64-bit files, 64-bit inums, 32-bit timestamps, 64-bit long ints,
    socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace,
    append, ACLs, xattrs, iconv, symtimes, prealloc

rsync comes with ABSOLUTELY NO WARRANTY.  This is free software, and you
are welcome to redistribute it under certain conditions.  See the GNU
General Public Licence for details.

rsync is a file transfer program capable of efficient remote update
via a fast differencing algorithm.

Usage: rsync [OPTION]... SRC [SRC]... DEST
  or   rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST
  or   rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST
  or   rsync [OPTION]... SRC [SRC]... rsync://[USER@]HOST[:PORT]/DEST
  or   rsync [OPTION]... [USER@]HOST:SRC [DEST]
  or   rsync [OPTION]... [USER@]HOST::SRC [DEST]
  or   rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]
The ':' usages connect via remote shell, while '::' & 'rsync://' usages connect
to an rsync daemon, and require SRC or DEST to start with a module name.

Options
 -v, --verbose               increase verbosity
     --info=FLAGS            fine-grained informational verbosity
     --debug=FLAGS           fine-grained debug verbosity
     --msgs2stderr           special output handling for debugging
 -q, --quiet                 suppress non-error messages
     --no-motd               suppress daemon-mode MOTD (see manpage caveat)
 -c, --checksum              skip based on checksum, not mod-time & size
 -a, --archive               archive mode; equals -rlptgoD (no -H,-A,-X)
     --no-OPTION             turn off an implied OPTION (e.g. --no-D)
 -r, --recursive             recurse into directories
 -R, --relative              use relative path names
     --no-implied-dirs       don't send implied dirs with --relative
 -b, --backup                make backups (see --suffix & --backup-dir)
     --backup-dir=DIR        make backups into hierarchy based in DIR
     --suffix=SUFFIX         set backup suffix (default ~ w/o --backup-dir)
 -u, --update                skip files that are newer on the receiver
     --inplace               update destination files in-place (SEE MAN PAGE)
     --append                append data onto shorter files
     --append-verify         like --append, but with old data in file checksum
 -d, --dirs                  transfer directories without recursing
 -l, --links                 copy symlinks as symlinks
 -L, --copy-links            transform symlink into referent file/dir
     --copy-unsafe-links     only "unsafe" symlinks are transformed
     --safe-links            ignore symlinks that point outside the source tree
     --munge-links           munge symlinks to make them safer (but unusable)
 -k, --copy-dirlinks         transform symlink to a dir into referent dir
 -K, --keep-dirlinks         treat symlinked dir on receiver as dir
 -H, --hard-links            preserve hard links
 -p, --perms                 preserve permissions
 -E, --executability         preserve the file's executability
     --chmod=CHMOD           affect file and/or directory permissions
 -A, --acls                  preserve ACLs (implies --perms)
 -X, --xattrs                preserve extended attributes
 -o, --owner                 preserve owner (super-user only)
 -g, --group                 preserve group
     --devices               preserve device files (super-user only)
     --copy-devices          copy device contents as regular file
     --specials              preserve special files
 -D                          same as --devices --specials
 -t, --times                 preserve modification times
 -O, --omit-dir-times        omit directories from --times
 -J, --omit-link-times       omit symlinks from --times
     --super                 receiver attempts super-user activities
     --fake-super            store/recover privileged attrs using xattrs
 -S, --sparse                turn sequences of nulls into sparse blocks
     --preallocate           allocate dest files before writing them
 -n, --dry-run               perform a trial run with no changes made
 -W, --whole-file            copy files whole (without delta-xfer algorithm)
     --checksum-choice=STR   choose the checksum algorithms
 -x, --one-file-system       don't cross filesystem boundaries
 -B, --block-size=SIZE       force a fixed checksum block-size
 -e, --rsh=COMMAND           specify the remote shell to use
     --rsync-path=PROGRAM    specify the rsync to run on the remote machine
     --existing              skip creating new files on receiver
     --ignore-existing       skip updating files that already exist on receiver
     --remove-source-files   sender removes synchronized files (non-dirs)
     --del                   an alias for --delete-during
     --delete                delete extraneous files from destination dirs
     --delete-before         receiver deletes before transfer, not during
     --delete-during         receiver deletes during the transfer
     --delete-delay          find deletions during, delete after
     --delete-after          receiver deletes after transfer, not during
     --delete-excluded       also delete excluded files from destination dirs
     --ignore-missing-args   ignore missing source args without error
     --delete-missing-args   delete missing source args from destination
     --ignore-errors         delete even if there are I/O errors
     --force                 force deletion of directories even if not empty
     --max-delete=NUM        don't delete more than NUM files
     --max-size=SIZE         don't transfer any file larger than SIZE
     --min-size=SIZE         don't transfer any file smaller than SIZE
     --partial               keep partially transferred files
     --partial-dir=DIR       put a partially transferred file into DIR
     --delay-updates         put all updated files into place at transfer's end
 -m, --prune-empty-dirs      prune empty directory chains from the file-list
     --numeric-ids           don't map uid/gid values by user/group name
     --usermap=STRING        custom username mapping
     --groupmap=STRING       custom groupname mapping
     --chown=USER:GROUP      simple username/groupname mapping
     --timeout=SECONDS       set I/O timeout in seconds
     --contimeout=SECONDS    set daemon connection timeout in seconds
 -I, --ignore-times          don't skip files that match in size and mod-time
 -M, --remote-option=OPTION  send OPTION to the remote side only
     --size-only             skip files that match in size
 -@, --modify-window=NUM     set the accuracy for mod-time comparisons
 -T, --temp-dir=DIR          create temporary files in directory DIR
 -y, --fuzzy                 find similar file for basis if no dest file
     --compare-dest=DIR      also compare destination files relative to DIR
     --copy-dest=DIR         ... and include copies of unchanged files
     --link-dest=DIR         hardlink to files in DIR when unchanged
 -z, --compress              compress file data during the transfer
     --compress-level=NUM    explicitly set compression level
     --skip-compress=LIST    skip compressing files with a suffix in LIST
 -C, --cvs-exclude           auto-ignore files the same way CVS does
 -f, --filter=RULE           add a file-filtering RULE
 -F                          same as --filter='dir-merge /.rsync-filter'
                             repeated: --filter='- .rsync-filter'
     --exclude=PATTERN       exclude files matching PATTERN
     --exclude-from=FILE     read exclude patterns from FILE
     --include=PATTERN       don't exclude files matching PATTERN
     --include-from=FILE     read include patterns from FILE
     --files-from=FILE       read list of source-file names from FILE
 -0, --from0                 all *-from/filter files are delimited by 0s
 -s, --protect-args          no space-splitting; only wildcard special-chars
     --address=ADDRESS       bind address for outgoing socket to daemon
     --port=PORT             specify double-colon alternate port number
     --sockopts=OPTIONS      specify custom TCP options
     --blocking-io           use blocking I/O for the remote shell
     --stats                 give some file-transfer stats
 -8, --8-bit-output          leave high-bit chars unescaped in output
 -h, --human-readable        output numbers in a human-readable format
     --progress              show progress during transfer
 -P                          same as --partial --progress
 -i, --itemize-changes       output a change-summary for all updates
     --out-format=FORMAT     output updates using the specified FORMAT
     --log-file=FILE         log what we're doing to the specified FILE
     --log-file-format=FMT   log updates using the specified FMT
     --password-file=FILE    read daemon-access password from FILE
     --list-only             list the files instead of copying them
     --bwlimit=RATE          limit socket I/O bandwidth
     --stop-at=y-m-dTh:m     Stop rsync at year-month-dayThour:minute
     --time-limit=MINS       Stop rsync after MINS minutes have elapsed
     --outbuf=N|L|B          set output buffering to None, Line, or Block
     --write-batch=FILE      write a batched update to FILE
     --only-write-batch=FILE like --write-batch but w/o updating destination
     --read-batch=FILE       read a batched update from FILE
     --protocol=NUM          force an older protocol version to be used
     --iconv=CONVERT_SPEC    request charset conversion of filenames
     --checksum-seed=NUM     set block/file checksum seed (advanced)
     --noatime               do not alter atime when opening source files
 -4, --ipv4                  prefer IPv4
 -6, --ipv6                  prefer IPv6
     --version               print version number
(-h) --help                  show this help (-h is --help only if used alone)

Use "rsync --daemon --help" to see the daemon-mode command-line options.
Please see the rsync(1) and rsyncd.conf(5) man pages for full documentation.
See http://rsync.samba.org/ for updates, bug reports, and answers

バックアップ実行コマンドと定期実行

バックアップコマンド(rsyncコマンド)

sudo rsync -aEv --delete-during --exclude-from=/rsync-exclude.txt / /mnt/synology/raspberry_backup/ --log-file=/home/rsync.log

crontabを編集

以下のcronジョブを登録します。
午前3時にrysyncコマンドを実行するように登録しました。
「sudo crontab -e」で編集します。

@raspberrypi:~ $ sudo crontab -e
0 3 * * * sudo rsync -a --delete-during --exclude-from=/rsync-exclude.txt / /mnt/synology/raspberry_backup/ --log-file=/home/rsync.log

[参考]cron コマンド

crontabファイルで、cron コマンドの実行を記述する行は、6つのフィールドで形成されています。(システムの crontabファイル(/etc/crontab)は、7つ(分、時、日、月、曜日、ユーザ名、コマンド))、コマンドの実行時間をさまざまな形式で指定することが出来ます。
初めてcrontabファイルを編集した場合には、何も書かれていませんので、以下のフォーマットで記述します。また、各フィールドでは、 '*' を使用することが可能です。

 分 時 日 月 曜日 コマンド
0~59
0~23
1~31
1~12 or jan~dec
曜日0~7 [0,7は日曜日] or sun~sat
コマンド有効なコマンドを記述します。空白を含むことも可能ですが、標準のBourne Shellの書式に従って記述します。

 

参考URL

Raspberry Piから外部ボリューム(NASをNFSマウント)利用

Raspberry Piからも信頼性の高いボリュームを利用したい、より大きなサイズのボリュームを利用したい、Raspberry Piの内容を外部にバックアップしたいなど、外部NASを利用したい利用シーンはたくさんあると思います。
今回は、WEBサーバなど各種サーバー機能のメインマシンであるSynologyのボリュームをNFSマウントし、利用する方法を記事にしておきます。

SynologyでNFS利用に向けた設定

SynologyでNFSを利用可能にする設定

「コントロールパネル」「ファイルサービス」「SMB/AFP/NFS」タブの「NFS」セッションの「NFSを有効にする」にチェックを入れて下さい。チェック後は、「適応」ボタンを押し、設定を反映して下さい。

SynologyでNFS利用設定1

共有フォルダにNFS権限付与

Synologyで作成した共有フォルダに対して、NFS権限の付与を行います。
「コントロールパネル」「共有フォルダ」対象の共有フォルダを選択して「編集」、「NFS権限」「編集」でNFSルールを指定します。
以下の例では、Raspberry Pi1台向け(IPアドレス:192.168.0.200)にNFS権限を付与しています。

SynologyでNFS利用設定2

共有フォルダのNFS権限画面に表示されている「マウントパス」は、実際のマウント時に必要となるので、内容のメモをお願いします。
マウント時のパス:<Synology_IPアドレス>:<マウントパス>
192.168.0.100:/volume1/raspberry

SynologyでNFS利用設定3

Raspberry PiからNASボリュームMount

Raspberry PiからmountコマンドでNASボリュームをNFSマウント

@raspberrypi:~ $ sudo apt-get install nfs-common
[sudo] password for:
nfs-common is already the newest version (1:1.3.4-2.5).
0 upgraded, 0 newly installed, 0 to remove and 26 not upgraded.

@raspberrypi:~ $ sudo showmount -e 192.168.0.100
Export list for 192.168.0.100:
/volume1/raspberry 192.168.0.200

@raspberrypi:~ $ sudo mkdir /mnt/synology

@raspberrypi:~ $ sudo mount -t nfs 192.168.0.100:/volume1/raspberry /mnt/synology

Raspberry Pi起動・再起動時のNFS自動マウント

/etc/fstabにNFSボリュームのマウント情報を記載

@raspberrypi:~ $ cat /etc/fstab
proc /proc proc defaults 0 0
/dev/mmcblk0p6 /boot vfat defaults 0 2
/dev/mmcblk0p7 / ext4 defaults,noatime 0 1
# a swapfile is not a swap partition, no line here
# use dphys-swapfile swap[on|off] for that
192.168.0.100:/volume1/raspberry /mnt/synology nfs defaults,_netdev 0 0

Raspberry PiのBootオプション変更(NASマウントタイミング変更)

Raspberry PiのBoot Option「Wait for Network at Boot Choose whether to wait for network」を変更します。「raspi-config」を用いて、設定を行います。

@raspberrypi:~ $ sudo raspi-config
  1. 3 Boot Options Configure options for start-up を選択
  2. B2 Wait for Network at Boot Chose whether to wait for network connection を選択
  3. <Yes> を選択
  4. <Ok> を選択

/etc/rc.localに3秒Sleep後マウント

上記の設定後も、起動・再起動時の自動マウントが実行されませんでした。
シスログ(/var/log/syslog)を確認したところ、やはり起動時のマウント時にNASへのネットワークマウントが失敗しておりました。
mount.nfs: Network is unreachable

Jul 9 06:36:22 raspberrypi rpc.mountd[473]: Version 1.3.3 starting
Jul 9 06:36:23 raspberrypi mount[489]: mount.nfs: Network is unreachable
Jul 9 06:36:23 raspberrypi systemd[1]: mnt-synology.mount: Mount process exited, code=exited, status=32/n/a
Jul 9 06:36:23 raspberrypi systemd[1]: mnt-synology.mount: Failed with result 'exit-code'.
Jul 9 06:36:23 raspberrypi systemd[1]: Failed to mount /mnt/synology.

よって、/etc/rc.localに3秒のスリープとマウントコマンドを追加しました。
sleep 3
sudo mount -t nfs 192.168.0.100:/volume1/raspberry /mnt/synology

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi

#After Sleep 3, then execute a mount command
sleep 3
sudo mount -t nfs 192.168.0.100:/volume1/raspberry /mnt/synology

exit 0

その他ブートシーケンスに関係すると
(/etc/rc.localにスリープおよびマウントを入れた際には試す必要ないです)

systemd-networkd-wait-onlineを利用し、確実にネットワークの起動後にサービスを起動が出来るようにwaitが入るようです。
systemd-networkdとsystemd-networkd-wait-onlineをenableにします。

@raspberrypi:~ $ sudo systemctl enable systemd-networkd
Created symlink /etc/systemd/system/dbus-org.freedesktop.network1.service → /lib/systemd/system/systemd-networkd.service.
Created symlink /etc/systemd/system/multi-user.target.wants/systemd-networkd.service → /lib/systemd/system/systemd-networkd.service.
Created symlink /etc/systemd/system/sockets.target.wants/systemd-networkd.socket → /lib/systemd/system/systemd-networkd.socket.
Created symlink /etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service → /lib/systemd/system/systemd-networkd-wait-online.service.
@raspberrypi:~ $ sudo systemctl enable systemd-networkd-wait-online