ソフラボの技術ブログ

仕事で使ったプログラミング、サーバー周りで役に立つこと、Webサービス開発に必要な技術情報、モバイル情報を書いてます。わかりやすく見やすくをモットーにしています。

MacでPostfixを使ってGmail経由でメールを送信する

メールを送信するサービス等を開発していると、メール送信テストをする必要が出てきます。
開発環境でPostfixを使ってGmail経由でメール送信する方法を紹介します。

環境

macOS:10.14.6
Postfix:3.3.2(デフォルトのまま)

補足

Postfixのバージョン確認

$ postconf | grep mail_version
mail_version = 3.2.2
milter_macro_v = $mail_name $mail_version

手順

1.main.cfでメールの設定を行う

$ sudo vi /etc/postfix/main.cf
relayhost = [smtp.gmail.com]:587
mail_spool_directory = /var/spool/mail
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_mechanism_filter = plain
smtp_tls_security_level = may

Postfix2.3以降は「smtp_use_tls = yes」ではなく「smtp_tls_security_level = may」を使います。

http://www.postfix-jp.info/trans-2.3/jhtml/postconf.5.html#smtp_use_tls

smtp_use_tls (デフォルト: no)
この機能はPostfix 2.2以降で使えます。Postfix 2.3以降では smtp_tls_security_level を代わりに使ってください。


2.Googleでアプリパスワードを取得する
以下のサイトを参考にアプリパスワードを取得します。
バイスは「mac」を選択します。
www.howtonote.jp


3.送信時の認証情報を設定する

# 認証情報の作成
$ sudo vi /etc/postfix/sasl_passwd
[smtp.gmail.com]:587 hoge@gmail.com:取得したパスワード

# 権限変更
$ sudo chmod 600 /etc/postfix/sasl_passwd

# ハッシュ化
$ sudo postmap /etc/postfix/sasl_passwd


4.スプールを設定する

# ディレクトリ作成
$ sudo mkdir /var/spool/mail

# 所有者・グループの変更
$ sudo chown ログインユーザー名:staff /var/spool/mail

# 権限の変更
$ sudo chmod 700 /var/spool/mail


5.Postfixをリロードする

# すでに起動している場合
$ sudo postfix reload

# 停止
$ sudo postfix start

# 起動
$ sudo postfix stop

5.メール送信テストを行う
送信前に後で説明するメール送信ログの出力を行います。

$ mail hoge@gmail.com(送信先アドレス)
Subject: test # タイトル
test # 本文
. # メールを送信する

6.メールが届いているか確認する

メール送信ログ

Macではmaillogが生成されないようなので、メール送信前に以下のコマンドを実行しておきます。

$ log stream --predicate  '(process == "smtpd") || (process == "smtp")' --info

次のようなメッセージが表示された場合、メール送信に失敗しています。

(host smtp.gmail.com[74.125.203.108] said: 530-5.7.0 Authentication Required. Learn more at 530 5.7.0  https://support.google.com/mail/?p=WantAuthError o8sm917374pjf.37 - gsmtp (in reply to MAIL FROM command))


送信が成功した場合、次のようなメッセージが表示されればOKです。

to=<hoge@gmail.com>, relay=smtp.gmail.com[2404:6800:4008:c01::6d]:587, delay=2.6, delays=0.02/0.04/1.5/1, dsn=2.0.0, status=sent (250 2.0.0 OK  1595305690 z25sm18507895pfg.140 - gsmtp)

MecurialをGitに変換する

愛用しているバージョン管理サービスのbitbucketでMercurialの提供を終了し、Gitに完全移行するということで、既存のMercurialリポジトリをGitに移行してみました。

記事を公開するまでの期間が長かったため、抜けや間違い等があるかもしれないので、参考程度にしてみてください。

動作環境

Mac
Homebrew

環境構築

MecurialをGitに変換するための環境を構築します。

Pythonの確認

$ python --version
Python 2.7.10

# インストールされてなければ
$ brew install python2

pipの確認(Pythonのパッケージ管理)

# インストール確認
$ pip
-bash: pip: command not found

# curlでpipを取得
$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 1764k  100 1764k    0     0  3484k      0 --:--:-- --:--:-- --:--:-- 3488k

# pipをインストール
$ python get-pip.py --user
WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.
Please see https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.
To avoid this problem you can invoke Python with '-m pip' instead of running pip directly.
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support
Collecting pip
  Using cached pip-20.0.2-py2.py3-none-any.whl (1.4 MB)
Collecting wheel
  Using cached wheel-0.33.6-py2.py3-none-any.whl (21 kB)
Installing collected packages: pip, wheel
  WARNING: The scripts pip, pip2 and pip2.7 are installed in '/Users/xxxxx/Library/Python/2.7/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.
  WARNING: The script wheel is installed in '/Users/xxxxx/Library/Python/2.7/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-20.0.2 wheel-0.33.6

# 環境変数を設定
$ vi ~/.bash_profile
export PATH=$PATH:$HOME/Library/Python/2.7/bin

# 環境変数の有効化
$ source ~/.bash_profile

PythonにMecurialをインストール

$ pip install mercurial
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support
Defaulting to user installation because normal site-packages is not writeable
Collecting mercurial
  Downloading mercurial-5.2.2.tar.gz (7.3 MB)
     |████████████████████████████████| 7.3 MB 2.3 MB/s 
Building wheels for collected packages: mercurial
  Building wheel for mercurial (setup.py) ... done
  Created wheel for mercurial: filename=mercurial-5.2.2-cp27-cp27m-macosx_10_14_intel.whl size=2728813 sha256=f0b70639c66146acc92e93b5d1325c68d5ab60eee4f62208d43ba4ad1f47821f
  Stored in directory: /Users/xxxxx/Library/Caches/pip/wheels/64/d4/95/33110d3cbdbafbd02d90913d03b12064730908962c08990d68
Successfully built mercurial
Installing collected packages: mercurial
Successfully installed mercurial-5.2.2

作業ディレクトリの作成

任意の場所に作業用ディレクトリを作成します。

MecurialをGitに変換する

変換ツールをダウンロードする

$ git clone https://github.com/frej/fast-export.git

変換

# Mecurialリポジトリに移動
$ cd HgRepo

# コミットログの登録ユーザー名を一覧に出力
$ hg log | grep user: | sort | uniq | sed 's/user: *//' > ../authors

# ユーザー名を変換でできる形式に変更
$ vi ../authors
# Mecurialでのユーザー一覧
User1
Yamada Taro
# 上記を以下の形式に書き換えて保存
"User1"="User1 <user1@gmail.com>"
"Yamada Taro"="Yamada Taro <yamada_taro@yahoo.co.jp>"

# Mecurialリポジトリ内でGitリポジトリを作成
$ git init

# 変換時にエラーが出るので設定を変更しておく
$ git config core.ignoreCase false

# 変換を実行する
$ ../fast-export/hg-fast-export.sh -r . -A ../authors

変換したソースをリモートにプッシュする

# リモート設定
$ git remote add origin https://xxxxx@bitbucket.org/xxxxx/GitRepo.git

# プッシュ
$ git push origin master

# タグを一括でプッシュ
$ git push origin master --tags

docker上のPHPでEXIFを使えるようにする

PHPで画像からEXIF情報を取得するには、exif_read_data関数を使います。
しかし、この関数だけでは動作しないのでEXIFモジュールを追加する必要があります。
docker上でEXIFモジュールを追加する方法をまとめました。

動作環境

Mac
・docker
・docker php:7.3-apache

手順

1.Dockerfileに以下を追記します。

RUN && apt-get install -y \
		libfreetype6-dev \
		libjpeg62-turbo-dev \
		libpng-dev \
        && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
	&& docker-php-ext-install -j$(nproc) gd  exif \

2.Dockerfileを実行しイメージを作成します。

3.PHPexif_read_dataを実行しエラーが出ないか確認します。

php.iniに設定を追加する必要はありません。Windows環境の場合、必要です。

MacBook Pro 2019 でログイン時に画面が乱れる現象

2019年末に6年使ったMacBook Proを2019年モデルに買い替えました。

バタフライキーボードに違和感を覚えながらも、使い続けるとタイピングが心地よくなってきて慣れてきました。

そんな中、使い出してから数回目の起動時に、画面が乱れる現象が発生するようになりました。

画面が乱れる現象とは?

本体起動時にまずパスワード入力画面が表示されます。
そこでパスワードを入力しエンターキーを押すと、データ読み込みの進捗バーが表示されます。
そのバーが半分を少し超えた所で一瞬画面が乱れます。
乱れた後は、何事もなかったかのようにデスクトップが表示されます。

画面が乱れたときの画像は次のような感じです。

f:id:shinsuke789:20200215194138p:plain
MacBook Proの画面の乱れ

ログイン時だけなのであまり気にしてなかったのですが、普通に使っている時も極稀に違うタイプの画面のチラつきが発生します。

対処方法は?

Macでは定番のメンテナンスツール「OnyX」でメンテナンスを行うと画面のチラつきは発生しなくなります。
しかし、数回起動を行うとまた同じような現象が発生します。
特に外部モニターに繋げた後の起動は必ず発生しているような感じです。

www.titanium-software.fr

f:id:shinsuke789:20200215194654p:plain
OnyXのメンテナンス画面


2020/05/16 追記
ディスクの暗号化(FileVault)をオフにすると発生しなくなりました。
f:id:shinsuke789:20200516114819p:plain

まとめ

対処方法を書きましたが、一時的な対処であり根本的な解決には至っていません。

ネットで調べるとMacのハードウェアリセットを試したけど効果がなかったとあったので、リセットはまだ試してません。

何かご存じの方いらっしゃいましたら、コメント頂けたら幸いです。

Gradle4.xから6.xで変更する依存関係の定義

Graldeを4.10.2から6.0.1にバージョンアップしたときに警告やエラーが出たので、そのときに変更した依存関係周りを簡単にまとめました。

個人的にGradleはあまり詳しくないので、今回影響があった部分のみの記載です。

さらに詳細を知りたい方は、参考サイトをご覧ください。

バージョンアップ時のエラー

Gradle6にしてビルド等を走らせると、lombokを使用している場合、認識されずコンパイルが通りません。
いつの間にかGradleの設定方法が変わっていたので、その辺を変更します。

依存関係

ライブラリの依存定義に使う設定名が以下のように変わっています。

compile -> implementation
compile files -> implementation files
runtime -> runtimeOnly
providedCompile -> compileOnly

注釈処理

annotationProcessor」というものを使う必要があります。
以下、Domalombokの場合です。

// Doma
annotationProcessor 'org.seasar.doma:doma:2.19.3' 
implementation 'org.seasar.doma:doma:2.19.3' 

// lombok
annotationProcessor 'org.projectlombok:lombok:1.18.8'
compileOnly 'org.projectlombok:lombok:1.18.8'

Domaの場合、追加で以下も必要です。書き方が変わっています。

task copyDomaResources(type: Sync)  {
    from sourceSets.main.resources.srcDirs
    into compileJava.destinationDir
    include 'doma.compile.config'
    include 'META-INF/**/*.sql'
    include 'META-INF/**/*.script'
}

compileJava {
	dependsOn copyDomaResources
}

警告の確認

古い定義でもそのまま使えますが、バージョンアップ毎に非推奨の定義があり、将来的に削除されます。
スムーズにバージョンアップするためにも、非推奨定義は別の定義に変更しておいた方が良いです。
非推奨や警告定義の確認は、Gradleの各コマンドで「--warning-mode all」を付与すると出力されます。

gradle build --warning-mode all

MW WP Formで登録後に自動で投稿する方法

WordPressのフォーム登録プラグイン「MW WP Form」を使って、登録後に自動で投稿する方法を紹介します。
この記事では、登録フォームの設定方法は割愛し、登録後の自動登録の部分のみ説明します。

環境

WordPress 5.2.4
MW WP Form 4.2.0
任意のテーマ

変更箇所

以下に記載のコードを使用中のテーマにある「function.php」に追加します。

フォームで使用するカスタムフィールドを定義する

管理画面の投稿登録画面に登録フォームで使用するカスタムフィールドの入力欄を追加します。
これを設定することで、投稿時に毎回プルダウンからカスタムフィールドを選択する手間が省かれます。

/**
 * 投稿画面で表示するカスタムフィールドのHTMLを定義します。
 * 第2引数で独自の引数を設定することで、textareaまたはtextを生成するようにしています。
 */
function write_html($post, $args) {
    $field_nm = $args['args'][0];
    $type = $args['args'][1];
	
    if ($type == 'textarea') {
        echo '<textarea name="' . $field_nm . '" style="width: 100%; height: 200px;">' . get_post_meta($post->ID, $field_nm, true) . '</textarea>';	
    }
    if ($type == 'text') {
        echo '<input type="text" name="' . $field_nm . '" style="width: 100%;" value="' . get_post_meta($post->ID, $field_nm, true) . '"/>';
    }

    wp_nonce_field( 'send_field1' , 'nonce_for_field_1' );
}

/**
 * 投稿画面にカスタムフィールドを表示します。
 * 最後の引数に配列で独自の引数を設定することができます。
 */
function add_custom_meta_box() {
    // 第1引数:項目ごとのdivタグにつくid名(被らないように任意の値)
    // 第2引数:登録フォームの各入力項目のname
    // 第3引数:HTMLを生成するコールバック名
    // 第4引数:適用するページの種別(postで)
    // 第5引数:メタボックスの種別(初期値:advanced)
    // 第6引数:メタボックスの優先度(初期値:default)
    // 第7引数:第3引数のコールバックで使う任意の引数(必要であればarrayで定義)
    add_meta_box('custom_field_1','氏名','write_html','post','advanced','default',array('氏名','text'));
    add_meta_box('custom_field_2','メールアドレス','write_html','post','advanced','default',array('メールアドレス','text'));
}
add_action('admin_menu', 'add_custom_meta_box');

フォーム登録後に投稿する

MW WP Formで作成した登録フォームからデータが登録されたら、投稿するようにします。
処理内容は、投稿を新規登録し、カスタムフィールドを別テーブルに登録します。

function update_meta_posts($Mail_admin, $Data) {

    // 登録フォームから入力データを取得
    $form_data = $Data->getInstance();

    // 投稿新規データを作成(タイトルと公開状態の2つの最低限)	
    $post = array(
        'post_title' => $form_data->get('募集タイトル'),
        'post_status' => 'publish'
    );
    // 上記データで投稿を新規登録
    $post_id = wp_insert_post($post, true);

    // 登録フォームのカスタムフィールドを別テーブルに登録
    update_post_meta($post_id, '氏名', $form_data->get('氏名'));
    update_post_meta($post_id, 'メールアドレス', $form_data->get('メールアドレス'));	
}
// アクションフックを追加
//   第1引数:末尾の数字は、登録フォーム作成時のショートコードを設定
//   第2引数:フック実行時の処理
//   第3・4引数:説明がないので用途は不明(固定値??)
add_action('mwform_before_send_admin_mail_mw-wp-form-8', 'update_meta_posts', 10, 2);

参考サイト

mwform_before_send_admin_mail_mw-wp-form-xxx | MW WP Form


エンジニアのためのWordPress開発入門

エンジニアのためのWordPress開発入門

LINQでグルーピングした最大の要素を取得する

C#でグルーピングして最大の要素を取得したかったので、試行錯誤して調べた結果、MaxではなくOrderByDescendingを使うことで実現できました。

まだまだLINQは、使い慣れてないのでちょっと特殊なことをしようとするとハマって時間が取られてしまいました…。そんなんでお困りの方、ご参考に。

少し解説すると、以下のようになります。
GroupBy
・第1引数:元の値を何でグループ化するかを指定する
・第2引数:元の値をグループ化したものに対して、キーと値の2つの引数が使え、値に対しての処理を指定する

OrderByDescending
元の値を第1引数で指定したもので昇順に並べ、Firstを指定することで先頭を取得する

サンプルコード

// 値を持つクラス
public class Hoge
{
    public int Key { get; set; }

    public string Dt { get; set; }
}

// グルーピングして最大を取得する処理
public void Main(string[] args)
{
    // 値の設定
    List<Hoge> list = new List<Hoge> {
	new Hoge() { Key = 1, Dt = "20190101" },
	new Hoge() { Key = 1, Dt = "20190102" },
	new Hoge() { Key = 2, Dt = "20190101" },
	new Hoge() { Key = 3, Dt = "20190310" },
	new Hoge() { Key = 3, Dt = "20190305" },
	new Hoge() { Key = 3, Dt = "20190301" },
	new Hoge() { Key = 4, Dt = "20190101" },
	new Hoge() { Key = 4, Dt = "20190102" },
	new Hoge() { Key = 5, Dt = "20190103" },
    };

    // Key毎にDtが最大の要素を取得
    List<Hoge> retList = list
	.GroupBy(
	    h => h.Key,
	    (k, v) => v.OrderByDescending(o => o.Dt).First())
	.ToList();

    // 結果
    retList.ForEach(f => Console.WriteLine("{0} {1}", f.Key, f.Dt));
}

結果

1 20190102
2 20190101
3 20190310
4 20190102
5 20190103

Key毎にDtが最大の要素がListに格納されています。