WinSCPでバッチ処理を行う

会社で使ってるプロジェクト管理ツールのRedMineのバックアップを全然やってなかったので、さすがにそろそろバックアップしとくかということで、シェルスクリプトを書いた。あんまりシェルスクリプト書かないので、こういう書き方が正しいのかはわからないが、一応動いているので、メモとして残しておく。ちなみに、運用として、日付でバックアップしていくとどんどん増殖していくので、7日分もあれば十分だろうと思われるので、曜日で管理する。また、ファイルサイズも大したことなかったので、非圧縮である。

まずは、シェルスクリプトを書く。

vi redmine-backup.sh
#!/bin/bash

#Apacheの停止
service httpd stop > /dev/null

# RedMineのファイルディレクトリを指定
ATTATCHDIR=/path/to/files

# バックアップ先ディレクトリを指定
BACKDIR=/path/to/backup

# MySQLrootパスワード
ROOTPASS=********

# 曜日を取得
int_youbi=`date +%w`

case "$int_youbi" in
# 日曜日
0)
        youbi="sun"
        ;;
# 月曜日
1)
        youbi="mon"
        ;;
# 火曜日
2)
        youbi="tue"
        ;;
# 水曜日
3)
        youbi="wed"
        ;;
# 木曜日
4)
        youbi="thu"
        ;;
# 金曜日
5)
        youbi="fri"
        ;;
# 土曜日
6)
        youbi="sut"
        ;;
esac

# MySQLのredmineデータベースのバックアップ
mysqldump -u root -p$ROOTPASS redmine > $BACKDIR"/redmine_"$youbi".dump"
# RedMineのファイルディレクトリのバックアップ
rsync -a $ATTACHDIR $BACKDIR > /dev/null

# ファイルの持ち主をWinSCP接続するユーザに変更
chown -R hoge. $BACKDIR
# Apacheの起動
service httpd start

これで、RedMineのバックアップを取るシェルスクリプトが出来た。
次に、rootのみの権限に変更した。

chmod 700 redmine-backup.sh

これを、cronに登録する。

echo "0 5 * * * root /root/redmine-backup.sh" > /etc/cron.d/backup

ここからWindows側の作業。
さきほどのシェルスクリプトで保存したデータをWindows側に自動に保存する。
通信方法はSSH。SSHクライアントとしてWinSCPを使う。

参考情報はこちら:WinScpをWindowsバッチで自動的に動かす方法

まずは、バッチファイルを作成する。ファイル名は、redmine.batとでもする。
ユーザ名とホスト名は、事前にWinSCP側に登録しておくこと。
/scriptで指定するファイルに、WinSCPに行わせる作業を記述する。

"C:\Program Files\WinSCP\WinSCP.exe" username@hostname /console /script=.\redmine.scr

次に、redmine.scrを記述する。
バッチ処理なので、上書き確認などはしないようにする。
バックアップ先は、E:\backup\redmineとする。

option batch on
option confirm off
option transfer binary
cd /path/to/backup/
lcd "E:\backup\redmine"
get -preservetime * .\
close
exit

redmine.batを実行してみて、ちゃんとダウンロードできたか確認する。
バックアップがダウンロードできているようだったら、
コントロールパネル > タスク > スケジュールされたタスクの追加
より、RedMineバックアップ用のタスクを作成する。基本的に1日1回だろうから、システム起動時に自動的にDLさせるようにしておいた。これで、RedMineサーバが故障してもデータの心配はなくなったと思われる。(サーバと俺のPCが同時にお亡くなりになったら終了だが。。。)


CakePHP1.2 Model::validates($data)の罠

CakePHP1.2の最新版である、1.2.1.8004にて、Model::validatesをオーバーライドして、モデル毎に独自のバリデーションを持たせていたのだが、いざ実験を行ってみると、全くバリデーションが効いていない。

<?php
    // コントローラー
    if ($this->Model->validates($this->data)) {
        // 検証OK
    } else {
        // 検証NG
    }
?>

上のように書いて、明らかにエラーになるような値を入力して渡しても、なぜかvalidatesをすり抜けてくる。

ちなみにモデル側は、

<?php
    // モデル
    function validates($data = array()){
        if(empty($data)){
            $data = $this->data;
        }
        parent::validates($data);
        // 独自のバリデーション
        
        if(count($this->validationErrors) > 0){
            return false;
        }else{
            return true;
        } 
    }
?>

である。独自に定義したバリデーション自体は通っていた。問題は、フレームワークが最初から持っているバリデーションを通っていないということだ。
うーむ、俺はCakePHP1.1のときに、ずっと上記のような書き方をしていたので、全く理由がわからない。
とりあえずググると、こんな記述を発見。

注意:英語のサイトです。
http://lemoncake.wordpress.com/2007/06/26/validation-gotcha-in-cakephp-12/

どうも、$this->Model->validates($this->data)のように引数を渡したのでは、ちゃんと動いてくれないっぽい。確かに、CakePHPポケットリファレンスでも、引数を渡すのは非推奨と書いてあった。validatesをする事前に、setをする必要があるらしい。

<?php
    // コントローラー
    $this->Model->set($this->data); // 事前にsetしておく
    if ($this->Model->validates()) {
        // 検証OK
    } else {
        // 検証NG
    }
?>

こうしたら、フレームワークが最初から持っているバリデーション機能を通ってくれた。
CakePHP1.1と1.2で、こんな違いもあるのだな~。それにしても、こんなことに2時間くらい悩んでしまった…。相当ストレスがたまってしまったが、原因がわかったし、解決できたから、報われたっす。

■追記
$this->Model->create($this->data)でもよいという記事があった。
参考情報を以下に載せておく。

<?php
    // コントローラー
    // 新規作成の場合は以下でもよい
    if ($this->Model->create($this->data) && $this->Model->validates()) {
        // 検証OK
    } else {
        // 検証NG
    }
?>

Cakephp1.2でModel::findの条件指定変更だったとは!

愛媛ITフェアに向けて、社内でオリジナルアプリを作ろうという話になった。開発チームでは、作成するのものの大枠が決定したので、とりあえずプロトタイプを作って、社内レビューして、よさそうだったら出展という流れにしようかなと思って、開発を開始した。

使用するフレームワークはCakePHP。これは勝手に俺が決めて、作っているだけだが、まあプロトタイプ作成だし、手段はどうだっていいということだ。RedMineのWiki上に要求仕様をまとめて、制作開始。

CakePHP1.2もstableが出たし、CakePHP徹底入門も結構前に買ってたので、CakePHP1.2で作ることにした。バージョンが1.1から1.2に変わったことで、便利になった部分も多いが、1.1を覚えてしまったせいで、結構弊害もあって、なかなか難しい。

CakePHP徹底入門
CakePHP徹底入門 イージーゲート

翔泳社 2008-08-29
売り上げランキング : 120410

おすすめ平均 star
starこれから始める人にはお勧め
starがっかり

Amazonで詳しく見る by G-Tools

この本だけでは、正直CakePHPの使い方をマスターできるとは到底思えないが、CakePHP1.2からスタートする人にとっては必須になるだろうなとは思う。個人的には、CakePHPガイドブックから入った人には、CakePHPポケットリファレンスのほうが便利に感じるだろう。

CakePHP ポケットリファレンス (Pocket Reference)
CakePHP ポケットリファレンス (Pocket Reference) 株式会社ブルーオーシャン 岡田 佳典

技術評論社 2008-06-18
売り上げランキング : 16855

おすすめ平均 star
starこれだけで作れそうです。
star現状のCakePHPリファレンスでは最良
starサンプルコードがもう少し丁寧であれば5つ星

Amazonで詳しく見る by G-Tools

さて、本題に入るが、CakePHP1.1のときと同じような感覚で、Model::findメソッドの条件指定に以下のように書いたら、動かなかった。

<?php
// コントローラークラス内での記述
$conditions = array(
    'name' => '= ' . $name,
    'limitdate' => '> ' . date('Y-m-d')
);
$modelInfo = $this->Model->find($conditions); // Modelは任意のModelクラス
if (empty($modelInfo)) { // 追記:emptyを書いたら2重になった
    // 条件に合うものがなかった処理
} else {
    // 条件に合うものが見つかった処理
}
?>

動かなかったというよりは、条件に合うものがなかった処理が実行されてしまった。
確実に条件に合うものがあるのに。デバッグモードを2にしていたため、SQL文を見たところ、条件部分が明らかにおかしくなっていたので、ググってみたら、以下のサイトを見つけた。

CakePHP 1.2 RC1からfindの条件指定方法が変更となった

このページに書かれているが、CakePHP1.2RC1から、Model::findの条件指定方法が変更になり、条件は連想配列の値側ではなく、キー側に持たせるようになった。これらは書籍にはない情報だったので、非常に助かった。先ほどの条件式を新しい方式に書き直すと、以下のようになる。

<?php
// コントローラークラス内での記述
$conditions = array(
    'name =' => $name, // ココを修正
    'limitdate >' => date('Y-m-d') // ココを修正
);
$modelInfo = $this->Model->find($conditions); // Modelは任意のModelクラス
if (empty($modelInfo)) { // 追記:emptyを書いたら2重になった
    // 条件に合うものがなかった処理
} else {
    // 条件に合うものが見つかった処理
}
?>

これで、見事に動いてくれた。CakePHPの情報は本だけではなかなか難しい。一番参考になるのは、やっぱりCookbookかな~と思う。

追記:syntax-highlighterで、emptyを書いたら、2重に書かれてしまった…。plainでみたら、1つなんですけどね。syntax-highlighterのバグかな。


MODx:Too many forward attempts!

MODxで新しいページを作って、アクセスしてみたら、エラーになった。

ERROR: Too many forward attempts!

The request could not be completed due to too many unsuccessful forward attempts.

なんだ?このメッセージは。

ググッっても、英語のページにしか辿り着かないが、とりあえずちょいちょい読んでみたら、

ツール->MODx設定->サイト

の、エラーページと権限外告知のページのIDが存在しないものになっていたので、そのせいだという。なので、早速ドキュメント作成を行い、エラー用のページを作成し、そのドキュメントのIDをエラーページと権限外告知のページとして設定したら、上記の英語でのエラーメッセージは表示されなくなり、先ほど作ったエラーページが表示されるようになった。

俺の目的は、新しく作ったページがエラー扱いされていることが問題なので、まだ解決はしていなかった。よくよく調べたら、フォルダとして作成したドキュメントのエイリアスが空だったため、フォルダの役割をしていなかったことが原因だった…。あ~、恥ずかしい。

ちなみに、エラーページは存在しないページを指定されたときに必ず表示されてしまうため、何かの拍子で検索エンジンに収集されたらマズイなと思ったので、エラーページのドキュメントにはmetaタグの

<meta content="noindex,nofollow" name="robots"/>

を追加しておいた。metaタグは、

リソース->metaタグとキーワードの管理

から作成することができる。


MODxのイベントログ対策

MODxにログインしてみたら、イベントログが溢れるくらい大きくなっていた。もちろん、エラーログだ。なぜこんなことになったのだろうか?色々と直していたはずなのに…。

どうもちゃんと動いているけれど、エラーログは裏でめちゃくちゃ出してるということだった。よく考えたら、WordPressを自動アップデートした影響かもしれないと思って、ログの意味を調べていたら、下記のサイトを見つけた。

http://tkfm.net/log/eid334.html

このサイトの情報を基に、=&を=に変更、 コンストラクタをコメントアウトしていった。冷静にログを見たら、コンストラクタが既にあるのに、また定義されてまっせーということだ。Wordpressとしては、PHP4とPHP5で動くようにということでしょうな。

これにて、ようやくイベントログが静かになった。
おちおちWordPressのバージョンアップもできんな~…。複雑な気分。