ftpしか使えない共用サーバに自動デプロイするには、lftpでミラーリングアップロードする

共用サーバでSSH接続が使える場合は、Jenkinsにジョブを作成して、rsyncやansibleで自動デプロイできます。

FTP接続しか使えない場合は、どのように自動デプロイすればいいでしょうか。

ftpの生コマンドを使うのはハードルが高そうね

ftpのミラーリングアップロードができるコマンドを探すと、lftpやwputがありました。今回、lftpを使って、自動デプロイを設定したので、lftpを説明します。

インストール方法

Ubuntu 18.04

$ sudo apt install lftp

$ lftp --version
LFTP | Version 4.8.1 | Copyright (c) 1996-2017 Alexander V. Lukyanov

LFTP is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with LFTP.  If not, see <http://www.gnu.org/licenses/>.

Send bug reports and questions to the mailing list <lftp@uniyar.ac.ru>.

Libraries used: GnuTLS 3.5.18, idn2 2.0.4, Readline 7.0, zlib 1.2.11Code language: Bash (bash)

MacOS X

$ brew install lftp

$ lftp --version
LFTP | Version 4.9.1 | Copyright (c) 1996-2020 Alexander V. Lukyanov
〜省略〜Code language: Bash (bash)

使い方

インタラクティブモード

lftpコマンドを起動すると、インタラクティブモードに入ります。プロンプトが「lftp :~>」に変化します。

$ lftp
lftp :~>Code language: Bash (bash)

(1)lftpのopenコマンドでFTPサーバに接続します。以下、ユーザ名、ホスト名は架空です。

ユーザ名とパスワードをカンマ区切りで並べます。

lftp :~>open -u admin@example.jp,password sv123.example.jp
lftp admin@example.jp@sv123.example.jp:~>Code language: Bash (bash)

(2)ローカルの deploy下を FTPサーバの /public_html下にミラーリングアップロードします。

lftp admin@example.jp@sv123.example.jp:~>cd /public_html
lftp admin@example.jp@sv123.example.jp:/public_html>mirror --reverse --verbose --overwrite  deploy/  ./

# 実際にはアップロードしないドライランの例
lftp admin@example.jp@sv123.example.jp:/public_html>mirror --dry-run --just-print --reverse --verbose --overwrite  deploy/  ./Code language: Bash (bash)

(3)FTPサーバの /public_html/cgi-bin/xxx.cgi を chmod a+x します。

lftp admin@example.jp@sv123.example.jp:/public_html>chmod a+x ./cgi-bin/xxx.cgiCode language: Bash (bash)

(4)FTPサーバの /public_html/tmp/* を 削除します。

lftp admin@example.jp@sv123.example.jp:/public_html>mrm ./tmp/*Code language: Bash (bash)

(5)lftpを終了します。

lftp admin@example.jp@sv123.example.jp:/public_html>exit
$Code language: Bash (bash)

openコマンド

openコマンドは、FTPサーバに接続します。

ユーザ名とパスワードをカンマ区切りで並べます。パスワードがカンマを含む場合、動作は不明です。

lftp :~>open -u admin@example.jp,password sv123.example.jp
lftp admin@example.jp@sv123.example.jp:~>Code language: Bash (bash)

lftp起動時のオプションでユーザ名、パスワード、ホスト名を指定することもできます。

$ lftp -u admin@example.jp,password sv123.example.jp
lftp admin@example.jp@sv123.example.jp:~>Code language: Bash (bash)

-u オプションを使わずに、ホストをftp://username:password@host 形式で指定することもできます。ユーザ名が "@" を含むときは、URLエンコードすることで、渡すことができました。

パスワードがカンマ","を含むときも、URLエンコードすることで、渡すことができそうです。

$ lftp ftp://admin%40example.jp,password@sv123.example.jp
lftp admin@example.jp@sv123.example.jp:~>Code language: Bash (bash)

help

lftp :~> help open
使い方: open [OPTS] <site>
Select a server, URL or bookmark
 -e <cmd>            execute the command just after selecting
 -u <user>[,<pass>]  use the user/password for authentication
 -p <port>           use the port for connection
 -s <slot>           assign the connection to this slot
 -d                  switch on debugging mode
 <site>              host name, URL or bookmark name
Code language: Bash (bash)

mirrorコマンド

mirrorコマンドは、FTPサーバのディレクトリ下をまるごとローカルにダウンロードしたり、ローカルのディレクトリ下をまるごとアップロードします。

mirrorコマンドのオプション説明
--reverseアップロード
--verbose経過を表示する。
--overwriteサーバー側にファイルが存在する場合、指定しないと、サーバー側のファイルを削除して、ローカルからアップロードする。
指定すると、サーバー側のファイルを削除せずに、ローカルからアップロードして上書きする。
--dry-run --just-printドライラン、実際にはアップロードしない
--parellel=2同時転送数を2にします。

--reverseオプションをつけないと、ダウンロードします。

--reverseオプションをつけると、ローカルのディレクトリ下をまるごとサーバ側にアップロードします。

--dry-run --just-print オプションをつけると、アップロードはしないけれども、どのようなファイルをアップロードするかを確認することができます。

サーバー側に同名ファイルが存在するとき、--overwriteオプションをつけないと、次のように、まず削除してから、アップロードします。

Removing old file `index.php'
Transferring file `index.php'
---> DELE index.php
<--- 250 DELE command successful
---> PROT C
<--- 200 Protection set to Clear                                                                   
---> PASV
<--- 227 Entering Passive Mode (???,???,???,???,???,???).                                   
---- データソケットを (???,???,???,???) のポート 60008 に接続中
---- Data connection established                                                          
---> STOR index.php
<--- 150 Opening BINARY mode data connection for index.php                
---- データソケットを閉じています
<--- 226 Transfer complete                                                                           
---> MFMT 20200329145023 index.php
<--- 213 Modify=20200329145023; index.php
---> SITE CHMOD 644 ./index.php
<--- 200 SITE CHMOD command successful            Code language: Bash (bash)

何か気になることでも?

わずかな時間ですが、ファイルがなくなる瞬間があります。その瞬間にファイルにアクセスしているユーザにはエラーを返す可能性があります。

--overwriteオプションをつけると、削除せずに、上書きアップロードします。

Overwriting old file `index.php'
Transferring file `index.php'
---> PROT C
<--- 200 Protection set to Clear
---> PASV
<--- 227 Entering Passive Mode (???,???,???,???).
---- データソケットを (???,???,???,???) のポート 60021 に接続中
---- Data connection established
---> STOR index.php
<--- 150 Opening BINARY mode data connection for index.php
---- データソケットを閉じています
<--- 226 Transfer complete
---> MFMT 20200330145316 index.php
<--- 213 Modify=20200330145316; index.php
---> SITE CHMOD 644 ./index.php
<--- 200 SITE CHMOD command successful     Code language: Bash (bash)

心配はなくなったのかしら?

残念ながら心配は残ります。ファイルがなくなりませんが、上書きでファイルオープンした直後はファイルの長さが0になる瞬間があります。

その瞬間にファイルにアクセスしているユーザがいると、なんらかのエラー、phpファイルならサーバーエラー、jsファイルならjsエラー、cssファイルならcssエラーになる可能性があります。

削除するよりはエラーになる可能性が低くなる思い、--overwriteオプションをつけました。

50歩100歩かもね

help

lftp :~> help mirror
使い方: mirror [OPTS] [remote [local]]

Mirror specified remote directory to local directory

 -R, --reverse          reverse mirror (put files)
Lots of other options are documented in the man page lftp(1).

When using -R, the first directory is local and the second is remote.
If the second directory is omitted, basename of the first directory is used.
If both directories are omitted, current local and remote directories are used.

See the man page lftp(1) for a complete documentation.
Code language: Bash (bash)

chmodコマンド

ファイルやディレクトリの権限モードを変更します。LinuxやMacOS Xのchmodコマンドとほぼ同じ使い方です。

モードは、0755のような8進数形式でも、a+w のような書式、どちらも指定できます。

lftp :~> chmod 0755 xxx.yy

lftp :~> chmod a+w  xxx.yy
Code language: Bash (bash)

help

lftp :~> help chmod
使い方: chmod [OPTS] mode file...
Change the mode of each FILE to MODE.

 -c, --changes        - like verbose but report only when a change is made
 -f, --quiet          - suppress most error messages
 -v, --verbose        - output a diagnostic for every file processed
 -R, --recursive      - change files and directories recursively

MODE can be an octal number or symbolic mode (see chmod(1))
Code language: Bash (bash)

mrmコマンド

mrmコマンドは、削除したいファイル名にワイルドカード"*"を指定できます。

mrmコマンドは、削除ファイルが見つからない場合、exitコードに非0を返します。Jenkinsジョブで実行するとき、非0はエラー扱いになってしまいます。

lftpのコマンドファイルの最後に「exit 0」とすることで、0を返すこともできますが、本当のエラーがわからなくなってしまいます。

正しいと思われる方法は、lftp の ls でファイル一覧を取得して、ファイルがあれば、lftp の mrm で削除する方法です。この方法は、lftpのコマンドファイルだけで完結できず、シェルスクリプトで判断しなければなりません。

そこで今回は、該当するディレクトリにempty.txtをアップロードしてから、mrmするようにしました。

help

lftp :~> help mrm
使い方: mrm <files>
ワイルドカード展開で指定されたファイルを削除します
Code language: HTML, XML (xml)

-e または -c オプション

オプション説明
-c続くコマンドを実行します。コマンドでexitしていなければ、lftpインタラクティブモードにとどまります。
-e続くコマンドを実行します。コマンドでexitしていなくても、lftpをexitします。

さきほどの(1)〜(5)を -cオプションまたは-eオプションで実行するには、次のように指定します。

コマンドとコマンドの区切りは、セミコロンか改行で区切ります。

シェルまたはシェルスクリプトで複数行にわたって指定するには、継続行の "\" が必要で、少々見づらいです。

$ lftp -c "\
open -u admin@example.jp,password sv123.example.jp;  \
cd /public_html;                                     \
mirror --reverse --verbose --overwrite  deploy/  ./; \
chmod a+x ./cgi-bin/xxx.cgi;                         \
mrm ./tmp/*;                                         \
exit;"Code language: Bash (bash)
$ lftp -e "\
open -u admin@example.jp,password sv123.example.jp;  \
cd /public_html;                                     \
mirror --reverse --verbose --overwrite  deploy/  ./; \
chmod a+x ./cgi-bin/xxx.cgi;                         \
mrm ./tmp/*;                                         \
"Code language: Bash (bash)

-f オプション

-cオプションや-eオプションに指定したlftpのコマンドを別ファイルにしておき、-f オプションで指定します。

例えば、deploy_lftp.txt で保存しておきます。シェルスクリプトではないので、継続行の "\" は必要ありません。

open -u admin@example.jp,password sv123.example.jp;
cd /public_html;
mirror --reverse --verbose --overwrite  deploy/  ./;
chmod a+x ./cgi-bin/xxx.cgi;
mrm ./tmp/*;Code language: Bash (bash)
$ lftp -f deploy_lftp.txt

実際に実行する前に、deploy_lftp.txtの内容を確認できるので、今回は -f オプションの方法にしました。

ステージング環境と本番環境

ステージング環境と本番環境で、FTP接続情報やFTPサーバの公開ディレクトリが違うことがあります。

例えば、ステージング環境は、FTPサーバの /が公開ディレクトリ、本番環境は、FTPサーバの /public_htmlが公開ディレクトリといった具合です。

最初のバージョン:2ファイル用意する方法

ステージング環境用のlftpコマンドファイル、本番環境用のlftpコマンドファイルを用意する方法です。

open -u admin@example.jp,password sv999.example.jp;
cd /;
mirror --reverse --verbose --overwrite  deploy/  ./;
chmod a+x ./cgi-bin/xxx.cgi;
mrm ./tmp/*;Code language: Bash (bash)
open -u admin@example.jp,password sv123.example.jp;
cd /public_html;
mirror --reverse --verbose --overwrite  deploy/  ./;
chmod a+x ./cgi-bin/xxx.cgi;
mrm ./tmp/*;Code language: Bash (bash)

最初のバージョンとしては悪くありませんが、問題が2つあります。

1つめは、下3行の同じ処理を2ファイルに書いてしまっていることです。

2つめは、パスワードを含む接続情報をファイルに記述していることです。接続情報は、環境変数から取得できるようにしたいところです。

バージョン2:m4マクロを使う

そこで、m4マクロでlftpコマンドファイルを作成することにしました。

m4はテキスト置換のためのマクロプロセッサで、Linuxに標準でインストールされています。

まず、m4マクロ deploy_lftp.m4 を用意します。このファイルの FTP_USER、FTP_PASS、FTP_HOST、DST_DIR、SRC_DIR、DRY_RUN を外部から与えて置換します。

open -u FTP_USER,FTP_PASS FTP_HOST
cd DST_DIR
mirror  DRY_RUN  --reverse  --verbose  --overwrite  SRC_DIR/  ./;
chmod a+x   ./cgi-bin/xxx.cgi;
mrm         ./tmp/*;
Code language: Bash (bash)

環境変数からm4マクロに渡してlftpコマンドファイルを作り、lftpを実行するシェルスクリプトを用意します。deploy_lftp.sh とします。chmod +x しておきます。

#!/bin/bash
set -u
set -e
set -x

mkdir --parent tmp
m4 \
  -D DRY_RUN="$DRY_RUN" \
  -D FTP_HOST=$FTP_HOST \
  -D FTP_USER=$FTP_USER \
  -D FTP_PASS=$FTP_PASS \
  -D SRC_DIR=$SRC_DIR \
  -D DST_DIR=$DST_DIR \
  deploy_lftp.m4 >tmp/deploy_lftp.txt

lftp  -d  -f tmp/deploy_lftp.txt
Code language: Bash (bash)

ステージング環境のJenkinsジョブのBuildシェルスクリプト

ここでは環境変数を使いました。ユーザ名やパスワードは、必要に応じてJenkinsのCredentialに登録して使ってください。

export FTP_HOST=stage.example.jp
export FTP_USER=admin@example.jp
export FTP_PASS=password
export SRC_DIR=deploy
export DST_DIR=/
# ドライラン
export DRY_RUN="--dry-run --just-print"
# 実際に実行するとき
export DRY_RUN=

./deploy_lftp.shCode language: Bash (bash)

ステージング環境のJenkinsジョブのBuildシェルスクリプト

export FTP_HOST=sv123.example.jp
export FTP_USER=admin@example.jp
export FTP_PASS=password
export SRC_DIR=deploy
export DST_DIR=/public_html
# ドライラン
export DRY_RUN="--dry-run --just-print"
# 実際に実行するとき
export DRY_RUN=

./deploy_lftp.shCode language: Bash (bash)

参考URL

GitHub - lavv17/lftp: sophisticated command line file transfer program (ftp, http, sftp, fish, torrent)
sophisticated command line file transfer program (ftp, http, sftp, fish, torrent) - lavv17/lftp
FTP でも rsync みたいにコマンドからファイル転送したい
ターミナルで何でも行う人にとって、FTP の GUI クライアントはもう辛すぎるのです。 コマンド1つで rsync のようにアップロードしたいのです。 同じように考
タイトルとURLをコピーしました