メインコンテンツへスキップ

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

·
Programming macOS Python

静的サイトジェネレーターは静的ファイルを生成します。WordPress とは異なり、記事公開まで数ステップ必要です。

  1. 記事を書く
  2. ビルドして全サイトデータを生成(※)
  3. レンタルサーバーにすべてのファイルをアップロード

だから、GitHub pages を使っている方もいらっしゃると思います。git push とともにデプロイされるのは楽ですからね。 Vercel を使うのもありでしょう。

でも、僕はすでに持っている独自ドメインや WordPress で使っているレンタルサーバーがあるのでこれらを流用したい。

そこで公開までさくっとやってくれるスクリプトを組みました。Pyftpsync を使い、 前回と同じく Automator を使ってアプリケーション化しています。

Pyftpsync とは>

Pyftpsync とは

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

▶︎ Pyftpsync

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

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

つまり Gridsome ではこうなります。

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

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

でもメリットもちゃんとあります。手動でアップロードするよりだんぜん楽ということです。

2023 年 1 月現在、SSG を Gridsome から Pelican に、さらに Hugo に移行しました。Gridsome の記述がありますが、Hugo で使っていません。本記事では Pyftpsync の使い方の紹介なので当日の記事のままとします。

Pyftpsync の使い方>

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>

参考: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