Python|Pyftpsyncを使ってローカルとリモートを同期する

Profile image of Takashi Hanamura
花村貴史 / Takashi Hanamura 2020/05/11 2020/04/05
Eye catching image for this article

このサイトは静的サイトジェネレーターのGridsomeでつくっています。SSG の特性上、記事公開までのステップはちょっと手間( ↓ )がかかります。

  1. 記事を書く
  2. gridsome build してサイトデータを再作成
  3. レンタルサーバーにすべてのファイルをアップロード

だから、Netlify や GitHub pages を使って運営するのがメジャーと思います。push とともにデプロイされるのは楽ですから。

Vercelというサービスが便利そうです。push とともに専用サーバーにデプロイされ、独自ドメインを持っていればリダイレクトもできるみたい( 20.05.05 追記)

でも、僕はすでに使っている「独自ドメイン」や「レンタルサーバー」があるのでこれらを流用したい。それじゃあ、と公開までさくっとやってくれるスクリプトを組みました。

Pyftpsync ライブラリを使い、前回と同じく、Automator を使ってアプリケーション化しています。

Pyftpsync とは

Martin Wendt さんがつくられている Python ライブラリで「ローカルとリモートを rsync コマンド風にやってくれるもの」と僕は理解しています。

▶︎ Pyftpsync

ただし、既知の制限があります。最たるものは 2 つ。

  1. 差分検知はファイルサイズと変更日から判断している
  2. ローカルフォルダ内に個別のメタデータファイルをつくり、最後の同期時刻とサイズを保存することで差分を検出する

このことから Gridsome を使っているとこうなります。

当初の僕の希望である「rsync コマンドのように差分だけがアップロードされればデプロイも短時間で済むじゃん」は達成できませんでした。

でもメリットもちゃんとあります。

手動でアップロードするよりだんぜん楽ということ。

Pyftpsync の使い方

公式のとおりに作ればとてもカンタン。使いやすい設計です。以下は同期モードの例で、他にアップロードモードがあります。

from ftpsync.ftp_target import FtpTarget
from ftpsync.targets import FsTarget
from ftpsync.synchronizers import BiDirSynchronizer


local = FsTarget("ローカルディレクトリパス")
remote = FtpTarget(
    "リモートディレクトリパス",
    "FTPサーバーアドレス",
    username="FTPアカウント",
    password="FTPパスワード",
    tls=True  # Trueの場合、FTPSが有効
)

# オプション設定例
opts = {
    "resolve": "local"  # コンフリクトした場合はローカルファイルを優先
}

# 同期の実行
sync = BiDirSynchronizer(local, remote, opts)
sync.run()

※デフォルトではコンソールにログ出力されますので、今何やっているかが分かります。

おわりに

WordPress や note を使ってきて、「公開までの仕組みがすべてつくられていること」ってすごいことだなと痛感しています。で、ここにきて SSG を使ってのサイト運営ですよ。

「手間かかることを楽しんでいる」感があります(笑)

でもね、その結果

という視点が磨かれてきましたし、なければつくってしまえ、という思考&行動パターンになってきました。エンジニアに復帰した僕としては、これはとても望ましい成長と思っています。

ひとつひとつ作っていく感覚は楽しいです。

最近はコロナのせいで自宅に籠る時間ができました。だからこそ、思いっきり勉強したり、思いっきり怠惰をむさぼったり、これまでの生活スタイルを進化させられるんじゃないか、と僕は思います。

たとえば、当たり前と言われているものの反対をやってみて、人間としての幅を広げられたらいいんじゃないかな。

「より良い未来のために、今できることをする」です。

参考: sync_gridsome.py

""" pyftpsyncライブラリを同期モードで使用し、Gridsomeでビルドしたデータ(dist/)をデプロイ先と同期する """

import configparser
import logging.handlers

from ftpsync.ftp_target import FtpTarget
from ftpsync.targets import FsTarget
from ftpsync.synchronizers import BiDirSynchronizer
from ftpsync.util import set_pyftpsync_logger


def sync_gridsome() -> None:
    """
    指定のローカルとリモートディレクトリを同期する
    """

    cfg = configparser.ConfigParser()
    cfg.read("config.ini")

    # ローカルとリモートの設定
    local = FsTarget(cfg["PATH"]["LOCAL"])
    user = cfg["FTPS"]["USER"]
    passwd = cfg["FTPS"]["PASSWORD"]
    remote = FtpTarget(
        cfg["PATH"]["REMOTE"],  # リモートディレクトリパス
        cfg["FTPS"]["SERVER"],  # FTPサーバ
        username=user,
        password=passwd,
        tls=True,  # FTPS有効
    )

    # オプション設定
    # ローカル優先/--deleteオプション有効/指定ディレクトリは同期除外
    # opts = {"resolve": "local", "delete": True, "force": True}
    opts = {"resolve": "local"}

    # 同期の実行
    sync = BiDirSynchronizer(local, remote, opts)
    sync.run()


if __name__ == "__main__":
    # ロガーの設定
    # pyftpsync.logにログを残す
    logger = logging.getLogger("sync.gridsome")
    log_path = "./pyftpsync.log"
    handler = logging.handlers.WatchedFileHandler(log_path)
    formatter = logging.Formatter(
        "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    )
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    set_pyftpsync_logger(logger)

    # 同期
    sync_gridsome()

▶ 最新版はGitHub