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

花村貴史のアイコン画像
Python

このサイトは静的サイトジェネレーターの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 を使っているとこうなります。

  • static配下の画像ファイルなど同一同名でも「差分あり」となる
  • ビルドするとdist配下のすべてのファイルが全削除&再生成されるため、上記2の効果がない
  • 結果、ほとんどのファイルがアップロード対象となる

当初の僕の希望である「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

#macOS#Python
花村貴史のアイコン画像

Software Engineer & Photographer

花村貴史

Takashi Q. Hanamura

Web開発をしながら、撮られることに慣れてない方の笑顔やステキな空気感をそっとすくい撮ってます。
横浜育ち38年、脱サラ信州移住2年半を経て、2018年春から関西在住。翌年秋から夫婦生活スタート。関西に来てよかったことは京都が近くなったこと。