end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

pythonで、任意のdirにライブラリのpathを通す

#!/usr/local/bin/perl
use strict;
use warnings;
use utf8;
use FindBin;
use File::Spec;
use lib File::Spec->catdir($FindBin::Bin, '../lib');
   :

perlでは上記のように「use lib , FindBin」を使用していましたが pythonでは、以下のように「import sys,os」や「sys.path.append()」を使うみたい。

init.py」の配備もポイントみたい

$ cat script/foo.py 
#!/usr/local/bin/python
# coding: utf-8
import sys,os
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../lib')

from TestClass import TestClass

def main():
    test_obj = TestClass(3)
    print test_obj.width


if __name__ == '__main__':
    main()
$ cat lib/TestClass.py
# coding: utf-8

class TestClass(object):
    def __init__(self, width): #constractor
        self.width = width
$ tree
.
├── lib
│   ├── __init__.py
│   └── TestClass.py
└── script
     └── foo.py

App::cpanminus と OrePAN で、localにcpan mirrorを作成し、そこからinstall

internetに接続されていない環境に 対象のperl moduleをinstallする必要があったので、OrePANを使ってみみた。

前準備

事前に本番機には、App::cpanminus (cpanmコマンド)、 開発機?には、OrePAN + App::cpanminus (cpanmコマンド)をinstallしておいて下さい。

開発機にcpan-mirror を作成し、本番機へscp

$ cpanm -L local-tmp --save-dists=cpan-mirror Plack::Middleware::ReverseProxy 
$ cpanm -L local-tmp --save-dists=cpan-mirror Amon2
$ cpanm -L local-tmp --save-dists=cpan-mirror DBI
$ cpanm -L local-tmp --save-dists=cpan-mirror DBD::SQLite
$ cpanm -L local-tmp --save-dists=cpan-mirror DBD::mysql
$ cpanm -L local-tmp --save-dists=cpan-mirror DBIx::Class
  :

↑こちらのようなコマンドで、search.cpan.org からmodule ダウンロードのみ行います。

その後、↓こちらのコマンドでmodule一覧(index file)を作成し、 cpan-mirrorを本番機へscpします。

$ orepan_index.pl -r ./cpan-mirror
$ tar -zcvf cpan-mirror.tar.gz cpan-mirror/
$ scp -i ~/.ssh/id_rsa cpan-mirror.tar.gz ???.???.???.???:tmp/

本番ではscpされたcpan-mirrorからinstall

後は簡単。

# cd /home/endo
# cpanm --mirror=file:///home/endo/cpan-mirror --mirror-only  Amon2
# cpanm --mirror=file:///home/endo/cpan-mirror --mirror-only  DBI
   :

mysql5.7 をsrcからinstall

end0tknr.hateblo.jp

以前、↑こちらで mysql5.5をinstallしましたが、5.7では少々異なりましたので

install mysql 5.7

$ wget http://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.14.tar.gz
$ tar -xvf mysql-5.7.14.tar.gz
$ cd mysql-5.7.14
$ cmake . \
   -DCMAKE_INSTALL_PREFIX=/usr/local/mysql \
   -DDEFAULT_CHARSET=utf8 \
   -DDEFAULT_COLLATION=utf8_general_ci \
   -DENABLED_LOCAL_INFILE=true \
   -DWITH_INNOBASE_STORAGE_ENGINE=1 \
   -DWITH_EXTRA_CHARSETS=all \
   -DWITH_READLINE=ON
  :
CMake Error at cmake/boost.cmake:81 (MESSAGE):
  You can download it with -DDOWNLOAD_BOOST=1 -DWITH_BOOST=<directory>

  This CMake script will look for boost in <directory>.  If it is not there,
  it will download and unpack it (in that directory) for you.

  If you are inside a firewall, you may need to use an http proxy:

  export http_proxy=http://example.com:80

Call Stack (most recent call first):
  cmake/boost.cmake:238 (COULD_NOT_FIND_BOOST)
  CMakeLists.txt:455 (INCLUDE)
-- Configuring incomplete, errors occurred!

mysql5.7よりboostが必要になったようです。

そこで、cmakeのoptionに 「-DDOWNLOAD_BOOST=1 -DWITH_BOOST=/home/endo/tmp/」を追加すると boostを勝手にdownloadしてくれます。

cmake . \
   -DCMAKE_INSTALL_PREFIX=/usr/local/mysql \
   -DDEFAULT_CHARSET=utf8 \
   -DDEFAULT_COLLATION=utf8_general_ci \
   -DENABLED_LOCAL_INFILE=true \
   -DWITH_INNOBASE_STORAGE_ENGINE=1 \
   -DWITH_EXTRA_CHARSETS=all \
   -DWITH_READLINE=ON \
   -DDOWNLOAD_BOOST=1 \
   -DWITH_BOOST=/home/endo/tmp/

$ make
$ make test
# make install

edit /etc/my.conf

ポイントは - skip-grant-tables がないと、id/pw=root/null でログインできません - sql-mode="ONLY_FULL_GROUP_BY" となっていると、selectする全colをgroup by で指定する必要があります

# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html
# *** DO NOT EDIT THIS FILE. It's a template which will be copied to the
# *** default location during install, and will be replaced if you
# *** upgrade to a newer version of MySQL.

[mysqld]

# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M

# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin

# These are commonly set, remove the # and set as required.
# basedir = .....
# datadir = .....
# port = .....
# server_id = .....
# socket = .....
default-storage-engine = INNODB
basedir = /usr/local/mysql
datadir = /home/mysql/data
port            = 3306
socket          = /tmp/mysql.sock

skip-grant-tables
#sql-mode="ONLY_FULL_GROUP_BY"
sql-mode=""

# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M 

# sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES 


# max connections
max_connections = 64
# table_open_cache = (max_connections * tables used in one transaction) + alpha
table_open_cache = 800
# table_definition_cache = (all tables in mysql + max_connections) + alpha
table_definition_cache = 400
# open_files_limit = table_open_cache * 1.4
open_files_limit = 1120
 
# global buffer
key_buffer_size = 16M
query_cache_type = 0
# innodb_buffer_pool_size = RAM for Mysql * 0.7
innodb_buffer_pool_size = 256M
 
# thread buffer
read_buffer_size = 256K
read_rnd_buffer_size = 512K
join_buffer_size = 256K
sort_buffer_size = 512K
 
# InnoDB
innodb_file_per_table
innodb_autoextend_increment = 64
innodb_log_files_in_group = 2
innodb_log_file_size = 64M
innodb_log_buffer_size = 16M
innodb_flush_log_at_trx_commit = 1
innodb_flush_neighbors=0
#innodb_flush_method=O_DIRECT
innodb_thread_concurrency = 4
innodb_commit_concurrency = 4
 
# log
general_log = 0
general_log_file = /usr/local/var/mysql/general.log
slow_query_log = 0
 
[mysqldump]
#max_allowed_packet = 16M
quick
set-charset
single-transaction

install apache2.2 + perl5.18 + mod_perl2.0.9

install perl 5.18

$ wget http://www.cpan.org/src/5.0/perl-5.18.4.tar.gz
$ tar -xvf perl-5.18.4.tar.gz
$ cd perl-5.18.4
$ ./Configure -Dusethreads -Accflags="-fPIC" -de
$ make
$ make test
$ su
# make install

※「-Dusethreads -Accflags="-fPIC"」option are needed by mod_perl.

install apache2.2

$ wget http://ftp.riken.jp/net/apache//httpd/httpd-2.2.31.tar.gz
$ tar -xvf httpd-2.2.31.tar.gz
$ cd httpd-2.2.31
$ ./configure --prefix=/home/endo/local/apache22 \
              --with-mpm=prefork \
              --enable-proxy \
              --enable-modules=all \
              --enable-so
$ make
$ make install

install mod_perl

$ wget http://www.apache.org/dyn/closer.cgi/perl/mod_perl-2.0.9.tar.gz
$ tar -xvf mod_perl-2.0.9.tar.gz
$ cd mod_perl-2.0.9
$ /usr/local/bin/perl Makefile.PL \
          USE_APXS=1 \
          WITH_APXS=/home/endo/local/apache22/bin/apxs \
          EVERYTHING=1
$ make
$ make test
$ su
# make install

edit httpd.conf & startup.pl

$ vi /home/endo/local/apache22/conf/httpd.conf
  :
LoadModule perl_module modules/mod_perl.so
PerlRequire /home/endo/local/apache22/conf/startup.pl

PerlSetEnv XING_CONF     /home/endo/dev/xing/etc/config.yaml

<Directory "/home/endo/dev/xing/app">
    AllowOverride All
    Order allow,deny
    Allow from all

    AuthType BASIC
    AuthUserFile /home/endo/dev/htpasswd
    AuthName "COLINUX MEMBERS"
    require valid-user

    <Files "*.pl">
       Options ExecCGI
       AddHandler cgi-script .pl
       SetHandler perl-script
       PerlHandler ModPerl::Registry
       PerlSendHeader On
    </Files>
</Directory>
Alias /Xing /home/endo/dev/xing/app
$ vi /home/endo/local/apache22/conf/startup.pl
  :
#/usr/local/bin/perl

BEGIN {
    use lib qw(.
               /home/endo/dev/xing/lib
             );
}

use NMW::Template;
$NMW::Template::ENCODING = 'utf8';
@NMW::Template::template_path=('/home/endo/dev/xing/tmpl');

use CGI;
$CGI::LIST_CONTEXT_WARN = 0;

#use Devel::Cover;
#$DEVEL_COVER_OPTIONS='-dir,/home/endo/tmp';

1;

nginx + nginx-auth-ldap module

step1/3 - install

$ cd /home/endo/tmp

$ wget http://nginx.org/download/nginx-1.9.3.tar.gz
$ tar -xvf nginx-1.9.3.tar.gz

$ wget https://www.openssl.org/source/openssl-1.0.2d.tar.gz
$ tar -xvf openssl-1.0.2d.tar.gz

$ git clone https://github.com/kvspb/nginx-auth-ldap.git

$ cd nginx-1.9.3
$ ./configure --prefix=/home/endo/local/nginx \
              --with-http_ssl_module \
              --with-openssl=../openssl-1.0.2d \
              --add-module=../nginx-auth-ldap
$ make
$ make install

step2/3 - edit nginx.conf

$ vi /home/endo/local/nginx/conf/nginx_auth_ldap.conf

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    ldap_server ldap_sexy {
        url ldap://ldap.sexy.co.jp/ou=people,o=sexy-group?uid?sub?(objectClass=*)
        group_attribute uniqueMember;
        group_attribute_is_dn on;
        require valid_user;
    }

    server {
        listen       8080;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            auth_ldap "AUTH_LDAP";
            auth_ldap_servers ldap_sexy;
            root   html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

step 3/3 - start nginx

$ cd /home/endo/local/nginx
$ ./sbin/nginx -c conf/nginx_auth_ldap.conf

VirtualBoxのストレージをvdmk(可変サイズ)→vdi形式(固定サイズ)に変更

vdi形式(固定サイズ)の方が、ディスクI/Oが良い気がするので、次のような手順でやってみた。

STEP1 ファイル → 仮想メディアマネージャ

f:id:end0tknr:20160818103848p:plain

STEP2 イメージファイルのコピー

※VDI(固定サイズ)、VDMK(可変サイズ) f:id:end0tknr:20160818103854p:plain f:id:end0tknr:20160818103857p:plain

STEP3 旧ストレージをun-mount、新ストレージをmount

f:id:end0tknr:20160818103904p:plain

以上!!

word2013にプログラムコードを貼るなら、セクション区切り + 行番号表示 + ページ罫線 + 行間隔調整

で、ソースがそれらしく見えます。

step1 ページレイアウト → セクション区切り + 行番号表示

f:id:end0tknr:20160810133916p:plain

step2 デザイン → ページ罫線

f:id:end0tknr:20160810133914p:plain

step3 ホーム → 行間隔調整

f:id:end0tknr:20160810133918p:plain

フォントの調整はお好みで

mysqlのLOAD DATA LOCAL INFILEによる bulk insert オレオレまとめ

sqlの"--"コメントにも記載していますが、自分としてのポイントは、

  • LOCAL INFILEの"LOCAL"で、"Access denied for user..."のようなエラーになりづらい
  • "\N"としなくても、NULLIF()でnull値をimportできます

かな?

LOAD DATA LOCAL INFILE
'/path/to/import_file.csv'
[REPLACE | IGNORE]
INTO TABLE import_tbl
character set 'utf8'              -- 文字コード
FIELDS TERMINATED BY ','
       OPTIONALLY ENCLOSED BY '"' -- フィールドを囲む文字
       ESCAPED BY '\'             -- エスケープ文字
LINES  TERMINATED BY '\n'
IGNORE 0 LINES                    -- 先頭?行を無視
(field_1,field_2,...)             -- import対象のcolumn
SET                               -- 必要に応じ値を変換
 update_time = NULLIF(update_time, '0000-00-00 00:00:00'), --日時の場合は注意
 field_1     = IF(field_1='A', field_1, null),
;

まぁ、dev.mysql.com/doc に記載の通りなんですけどね。 https://dev.mysql.com/doc/refman/5.6/ja/load-data.html

(再)Amon2 for perl のControllerに対するTest::Moreによるunit test

end0tknr.hateblo.jp

↑こんな風にplug-inを書いて頑張らなくても Plack::Util::load_psgi や Test::WWW::Mechanize::PSGI で十分実現できるそうです。↓

use strict;
use warnings;
use utf8;
use HTTP::Cookies;
use JSON;
use Plack::Test;
use Plack::Util;
use Test::More;
use Test::Requires 'Test::WWW::Mechanize::PSGI';
use Data::Dumper;

$ENV{"PLACK_ENV"} = 'development';

my $cookie_jar = HTTP::Cookies->new(file => "./cookie.txt",
                                    autosave => 1,
                                    ignore_discard => 1);
$cookie_jar->set_cookie(undef,                  #version
                        "HTTP_mavi-id",         #key
                        'ushiro',               #val
                        "/",                    #pathu
                        "localhost.local");     #domain

my $app = Plack::Util::load_psgi 'script/ean-server';
my $mech = Test::WWW::Mechanize::PSGI->new(app => $app,
                                           cookie_jar=>$cookie_jar);

subtest 'sub_test_name_1'=> sub {
    $mech->get_ok("/");
    $mech->title_like(qr/トップ/s);
    $mech->content_like(qr/ean/s);
};

subtest 'sub_test_name_2'=> sub {
    $mech->get_ok("/report/group/settings");
    my $res_data = JSON::from_json( $mech->content );

    print STDERR Dumper($res_data);
    
};

done_testing;

Amon2 for perl のControllerに対するTest::Moreによるunit test

以下の get_dummy_context() 内に記載しているようにAmon2クラスに対して、plug-inを書くとOK

use strict;
use utf8;
use t::Util;
use CGI;
use Date::Calc;
use JSON;
use Plack::Session;
use Plack::Test;
use Plack::Util;
use Test::More;
use Data::Dumper;

Ean->bootstrap;
use_ok('Ean::Controller::MemberEdit');
use_ok('Ean::ObjModel::Member');

my $SMART_ID = 'mae';
my $CGI;
my $SESSION;
my $REQUEST;


subtest 'confirm' => sub {

    my $class = 'Ean::Controller::MemberEdit';

    my $c = get_dummy_context();
    $c->session->set( navi_id => $SMART_ID );

    $c->req->param( 'navi_email_send', 1 );
    $c->req->param( 'ean_email1',      'ndds-test@example.com' );
    $c->req->param( 'ean_email1_conf', 'ndds-test@example.com' );
    $c->req->param( 'ean_email1_send', 1 );
    my $ret_data = $class->confirm($c);
    #JSONで返す型(int)もcheck
    like($ret_data, qr/"ean_email2_send":1/o);
    like($ret_data, qr/"show_bems_area_1":1/o);
    like($ret_data, qr/"show_bems_area_2":1/o);
    like($ret_data, qr/"navi_email_send":1/o);
    like($ret_data, qr/"ean_email1":"ndds-test\@example.com"/o);
    like($ret_data, qr/"ean_email1_conf":"ndds-test\@example.com"/o);
    like($ret_data, qr/"ean_email1_send":1/o);
    like($ret_data, qr/"ean_email2":"ndds-test2\@example.com"/o);
    like($ret_data, qr/"ean_email2_conf":"ndds-test2\@example.com"/o);

};


sub date_str {
    my ($day_diff) = @_;
    my @date = Date::Calc::Add_Delta_Days(Date::Calc::Today, $day_diff||0);
    return sprintf("%04d-%02d-%02d", @date);
}

sub now_str {
    my ($day_diff) = @_;
    my @datetime =
        Date::Calc::Add_Delta_YMDHMS(Date::Calc::Today_and_Now,
                                     0,0,$day_diff||0, 0,0,0);
    return sprintf("%04d-%02d-%02d %02d:%02d:%02d", @datetime);
}


sub get_dummy_context {
    no strict 'refs';
    no warnings 'redefine';

    *{"Ean\::session"} =
        sub {
            return $SESSION if $SESSION;

            my $dummy_env = {'psgix.session'=>{},
                             'psgix.session.options'=>{}};
            $SESSION = Plack::Session->new( $dummy_env );
            return $SESSION;
        };
    *{"Ean\::req"} =
        sub {
            return $CGI if $CGI;

            $CGI = CGI->new();
            return $CGI;
        };

    *{"Ean\::render_json"} =
        sub {
            my ($self,$perl_obj) = @_;
            return JSON::to_json($perl_obj);
        };

    *{"Ean\::request"} =
        sub {
            return $REQUEST if $REQUEST;

            $REQUEST = DummyRequest->new();
            return $REQUEST;
        };
    
    my $c = Ean->bootstrap;
    return $c;
}


done_testing;


package DummyRequest;

sub new {
    my ($class) = @_;
    my $self = {};
    $self =  bless $self, $class;
    return $self;
}

sub cookies { return {}; }
sub env { return {}; }

__DATA__

JSON for perl ? における 文字列(string) or 数値(numeric, integer)の判別方法

サーバからjsonを受け取った javascript が、 数値(例:2)を期待し厳密等価演算子 (===)でvalidateしたところ、 文字列(例:"2")だった為、false となったことがきっかけです。

perl製サーバアプリとしては、$val += 0 により、数値化( numfy )しましたが、 数値と文字列を区別なく扱えるperlで、JSON.pm は、 どのようにして、文字列(string) or 数値(numeric, integer)の判別しているのでしょう?

JSON for perl は、文字列 or 数値を判別し、json

#!/usr/local/bin/perl
use strict;
use JSON;

main();
sub main {
    print JSON::encode_json({val=>'10'}),"\n";
    print JSON::encode_json({val=>10}),"\n";
}
1;

↑こう書くと、↓こう出力されます

$ ./test_encode_json.pl 
{"val":"10"}
{"val":10}

JSON from perl のドキュメントには文字列 or 数値を判別できる旨が記載

http://search.cpan.org/perldoc?JSON に次のように記載されていますが、 一体、どのように実装されているのでしょう?

number A JSON number becomes either an integer, numeric (floating point) or string scalar in perl, depending on its range and any fractional parts. On the Perl level, there is no difference between those as Perl handles all the conversion details, but an integer may take slightly less memory and might represent more values exactly than floating point numbers.

If the number consists of digits only, JSON will try to represent it as an integer value. If that fails, it will try to represent it as a numeric (floating point) value if that is possible without loss of precision. Otherwise it will preserve the number as a string value (in which case you lose roundtripping ability, as the JSON number will be re-encoded to a JSON string).

JSONのバックエンドにある JSON::PP は B moduleを利用

JSONは、バックエンドに JSON::XSまたはJSON::PP を使用しますが JSON::PPはB moduleを利用して、数値or文字列を判別しています。

具体的には、B for perlを利用して、 B::SVp_IOK , B::SVp_NOK , B::SVp_POK とビット演算することで判別しています。

perlスカラー値には、整数 (IV)、符号なし整数 (UV)、 倍精度 (NV)、文字列 (PV)、他スカラ (SV) があります。

sub value_to_json {
    my ($self, $value) = @_;

    return 'null' if(!defined $value);

    my $b_obj = B::svref_2object(\$value);  # for round trip problem
    my $flags = $b_obj->FLAGS;
    ######## HERE !!!! ########
    return $value # as is 
        if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV?

    my $type = ref($value);

    if(!$type){
        return string_to_json($self, $value);
    }
    elsif( blessed($value) and  $value->isa('JSON::PP::Boolean') ){
        return $$value == 1 ? 'true' : 'false';
    }
    elsif ($type) {
        if ((overload::StrVal($value) =~ /=(\w+)/)[0]) {
            return $self->value_to_json("$value");
        }

        if ($type eq 'SCALAR' and defined $$value) {
            return   $$value eq '1' ? 'true'
                   : $$value eq '0' ? 'false'
                   : $self->{PROPS}->[ P_ALLOW_UNKNOWN ] ? 'null'
                   : encode_error("cannot encode reference to scalar");
        }

         if ( $self->{PROPS}->[ P_ALLOW_UNKNOWN ] ) {
             return 'null';
         }
         else {
             if ( $type eq 'SCALAR' or $type eq 'REF' ) {
                encode_error("cannot encode reference to scalar");
             }
             else {
                encode_error("encountered $value, but JSON can only represent references to arrays or hashes");
             }
         }

    }
    else {
        return $self->{fallback}->($value)
             if ($self->{fallback} and ref($self->{fallback}) eq 'CODE');
        return 'null';
    }

}

http://search.cpan.org/perldoc?JSON%3A%3APP

自分で、文字列(string) or 数値(numeric, integer)を判別するには?

次のように書けはOKです

#!/usr/local/bin/perl
use strict;
use B ();

main();
sub main {

    for my $value (10, "10", 10.0,  10.1, "10"){
        my $b_obj = B::svref_2object(\$value);
        my $flags = $b_obj->FLAGS;
        my $comp_1 = ($flags & ( B::SVp_IOK | B::SVp_NOK )),"\n";
        my $comp_2 =  ( $flags & B::SVp_POK ),"\n";
        if( $comp_1 and not $comp_2){
            print "$value is NUMERIC\n";
        } else {
            print "$value is            STRING\n";
        }
    }
}

install redmine + nginx + unicorn

前準備 - 必要moduleのinstall

# yum groupinstall "Development Tools"
# yum install openssl-devel readline-devel zlib-devel curl-devel \
      libyaml-devel ImageMagick ImageMagick-devel
# yum install ipa-pgothic-fonts
# yum install ruby ruby-devel

install redmine

と言っても、wgetして解凍するだけ

$ cd ~/dev
$ wget http://www.redmine.org/releases/redmine-3.3.0.tar.gz
$ tar -zxvf redmine-3.3.0.tar.gz
$ ln -s redmine-3.3.0  redmine

redmine 用 database準備

mysql> create database redmine CHARACTER SET utf8;
mysql> GRANT ALL ON redmine.* to redmine@localhost;
mysql> FLUSH PRIVILEGES;
mysql> SET PASSWORD FOR redmine@localhost=password('????');

redmine 設定と、追加module install

db接続と、gmail smtpによるメール設定

$ cd ~/dev/redmine/redmine/config
$ cp database.yml.example database.yml
$ vi database.yml
production:
  adapter: mysql2
  database: redmine
  host: localhost
  username: redmine
  password: "????"
  encoding: utf8
  
$ cp configuration.yml.example configuration.yml
email_delivery:
  delivery_method: :smtp
  smtp_settings:
    enable_starttls_auto: true
    address: "smtp.gmail.com"
    port: 587
    domain: "smtp.gmail.com" 
    authentication: :plain
    user_name: "????@gmail.com"
    password: "????"
# gem install io-console
# gem install bundler --no-rdoc --no-ri
   ## --no-rdoc --no-ri はドキュメント不要のため
# gem install json
# yum install mysql-devel
# gem install mysql2

$ cd ~/dev/redmine
$ bundle install --path vendor/bundler --without development test
$ bundle exec rake generate_secret_token
$ bundle exec rake db:migrate RAILS_ENV=production

ruby application server - unicorn

$ ~/dev/redmine
$ vi Gemfile
gem "unicorn"   #<-ADD

$ bundle update

config unicorn

$ vi /home/end0tknr/dev/redmine/config/unicorn.rb

# -*- coding: utf-8 -*-
worker_processes 4
timeout 900

listen 6000
pid '/home/end0tknr/logs/unicorn.pid'

stderr_path '/home/end0tknr/logs/unicorn.log'
stdout_path '/home/end0tknr/logs/unicorn.log'

preload_app true
GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true

before_fork do |server, worker|
defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!

old_pid = "#{ server.config[:pid] }.oldbin"
unless old_pid == server.pid
  begin
   sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
   Process.kill :QUIT, File.read(old_pid).to_i
   rescue Errno::ENOENT, Errno::ESRCH
  end
end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

config nginx

# vi /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;
    server_tokens off;
    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    include /etc/nginx/conf.d/*.conf;

    index   index.html index.htm;

   server {
     listen       80;
     location /favicon.ico {
       alias /home/ec2-user/dev/Splats/static/img/favicon.ico;
       break;
       access_log off;
     }

     location / {
       return 302 https://$host$request_uri;
     }
   }

  server {
    listen 443 ssl;
 
    ssl_certificate /etc/letsencrypt/live/????.????.mydns.jp/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/????.????.mydns.jp/privkey.pem;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout 5m;
 
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
 
    root /usr/share/nginx/html;
 
    index index.html index.htm index.nginx-debian.html;

    allow ???.???.???.0/24;
    deny  all;
 
    server_name _;
        location /redmine {
            proxy_pass      http://127.0.0.1:6000;

            auth_basic "MEMBER ONLY";
            auth_basic_user_file /home/ec2-user/dev/htpasswd;

            proxy_set_header Host             $host;
            proxy_set_header X-Real-IP        $remote_addr;
            proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-User $remote_user;
        }
  }
}

start uncorn , nginx

「 ????.????.mydns.jp/redmine 」でアクセスする為、「 --path /redmine」を起動時に指定しています。

$ cd /home/ec2-user/dev/redmine
$ bundle exec unicorn_rails -D --path /redmine \
   -c config/unicorn.rb -E production

$ sudo su -
# /etc/rc.d/init.d/nginx start

access to https://????.????.mydns.jp/redmine
initial user'ss id/pw = admin/admin

後は、以下のurlを参照。

http://redmine.jp/tech_note/first-step/admin/

Net::SMTPS for perl による Gmail SMTP利用には、アプリパスワードが必要

end0tknr.hateblo.jp 以前↑このように書いて動作していた気がしますが、 googleに2段階認証が導入された為でしょううか? 動作しなくなっていたので、以下のように修正しました。

#!/usr/local/bin/perl
use strict;
use utf8;
use FindBin;
use File::Spec;
use lib File::Spec->catdir( $FindBin::Bin, '../lib' );
use Encode;
use Net::SMTPS;
use MIME::Base64;
use Data::Dumper;

my $SMTP_CONF = {
    host     => 'smtp.gmail.com',
    port     => '587',
    from     => 'ないしょ@gmail.com',
    auth_uid => 'ないしょ@gmail.com',

    ### NOTICE!! application password
    ### https://myaccount.google.com/security#signin
    ### https://support.google.com/mail/answer/14257
    auth_pw  => 'ここは、アプリパスワード'
};

main();

sub main {
    my $ssl = 'starttls';    # 'ssl' / 'starttls' / undef

    my $smtp = Net::SMTPS->new(
        $SMTP_CONF->{host},
        Port  => $SMTP_CONF->{port},
        doSSL => $ssl,
        Debug => 1
    );

    $smtp->auth( $SMTP_CONF->{auth_uid}, $SMTP_CONF->{auth_pw} )
        or die "can't login smtp server";

    my $mailto = ['ないしょ@gmail.com'];
    my $mailto_str = join( ',', @$mailto );

    my $subject_org = 'これはテストです';
    my $subject = Encode::encode( 'MIME-Header-ISO_2022_JP', $subject_org );

    my $message = <<EOF;
このメールはテストです
EOF

    #メールのヘッダーを構築
    my $header = << "MAILHEADER_1";
From: $SMTP_CONF->{from}
Return-path: $SMTP_CONF->{from}
Reply-To: $SMTP_CONF->{from}
To: $mailto_str
MAILHEADER_1

    $header .= <<"MAILHEADER_2";
Subject: $subject
Mime-Version: 1.0
Content-Type: text/plain; charset = "ISO-2022-JP"
Content-Transfer-Encoding: 7bit
MAILHEADER_2

    $message = encode( 'iso-2022-jp', $message );

    $smtp->mail( $SMTP_CONF->{from} );
    $smtp->to(@$mailto);
    $smtp->data();
    $smtp->datasend("$header\n");
    $smtp->datasend("$message\n");
    $smtp->dataend();
    $smtp->quit;
}

アプリパスワードでなく、通常?のgoogleのログインパスワードを使用すると次のようなエラーとなります。

$ ./test_send_mail_by_gmail_smtp.pl 
Net::SMTPS>>> Net::SMTPS(0.03)
Net::SMTPS>>>   IO::Socket::INET(1.33)
Net::SMTPS>>>     IO::Socket(1.36)
Net::SMTPS>>>       IO::Handle(1.34)
Net::SMTPS>>>         Exporter(5.68)
Net::SMTPS>>>   Net::SMTP(3.05)
Net::SMTPS>>>     Net::Cmd(3.05)
Net::SMTPS>>>     IO::Socket::IP(0.37)
Net::SMTPS=GLOB(0x2b41ae0)<<< 220 smtp.gmail.com ESMTP i69sm1748185pfk.30 - gsmtp
Net::SMTPS=GLOB(0x2b41ae0)>>> EHLO localhost.localdomain
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-smtp.gmail.com at your service, [61.21.205.219]
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-SIZE 35882577
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-8BITMIME
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-STARTTLS
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-ENHANCEDSTATUSCODES
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-PIPELINING
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-CHUNKING
Net::SMTPS=GLOB(0x2b41ae0)<<< 250 SMTPUTF8
Net::SMTPS=GLOB(0x2b41ae0)>>> STARTTLS
Net::SMTPS=GLOB(0x2b41ae0)<<< 220 2.0.0 Ready to start TLS
Net::SMTPS=GLOB(0x2b41ae0)>>> EHLO localhost.localdomain
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-smtp.gmail.com at your service, [61.21.205.219]
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-SIZE 35882577
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-8BITMIME
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-ENHANCEDSTATUSCODES
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-PIPELINING
Net::SMTPS=GLOB(0x2b41ae0)<<< 250-CHUNKING
Net::SMTPS=GLOB(0x2b41ae0)<<< 250 SMTPUTF8
Net::SMTPS=GLOB(0x2b41ae0)>>> AUTH LOGIN
Net::SMTPS=GLOB(0x2b41ae0)<<< 334 VXNlcm5hbWU6
Net::SMTPS=GLOB(0x2b41ae0)>>> c21hcnQ4MTZwaWthQGdtYWlsLmNvbQ==
Net::SMTPS=GLOB(0x2b41ae0)<<< 334 UGFzc3dvcmQ6
Net::SMTPS=GLOB(0x2b41ae0)>>> 
Net::SMTPS=GLOB(0x2b41ae0)<<< 535-5.7.8 Username and Password not accepted. Learn more at
Net::SMTPS=GLOB(0x2b41ae0)<<< 535 5.7.8  https://support.google.com/mail/answer/14257 i69sm1748185pfk.30 - gsmtp