GitHubの自分の全リポジトリをNASに自動バックアップする
TL;DR
- GitHubに保管しているリポジトリを、自宅NASに自動で同期するバックアップシステムを作りました
- NAS(Synology製)にGitとPythonをインストールし、NASだけでバックアップのためのスクリプトを定期実行します
書いたコードはこちらのリポジトリにあります。
GitHubに置いたデータをローカルでも保管したい
クラウド全盛の時代に逆行するようですが、GitHubの自分の全リポジトリをローカルにバックアップしたくなりました。
理由はいくつかあります。
- データがクラウドだけにある状態は不安
- GitHubが落ちることもたまにはある
- PCのローカルリポジトリがバックアップとして万全とは限らない
① データがクラウドだけにある状態は不安
人によってデータに関する考え方は違うと思いますが、筆者自身は「自分が持つデータは全てローカルストレージに置いておきたい」派です。クラウドも便利に活用した上で、必ず同じデータを自室のPCやNAS、外付けHDDにも保管するようにしています。
もちろんクラウドストレージは便利で、データの保護についても個人宅よりも大企業のデータセンターのほうが上でしょう。しかし、クラウドストレージには検閲やアカウント凍結のリスクがつきまといます。
例えば、Google Driveに保管したファイルが誤って「不適切だ」と判断されGoogleアカウントが凍結されてしまった事例があります。
また、近年発達している音楽・動画配信サービスでも、アーティストが逮捕されてしまうと、過去の楽曲まで配信停止されて突然聴けなくなることが繰り返されています。
GitHubに保管されているものはソースコードがメインですから、違法なコンテンツと見なされることはそれほどないかもしれません。ただ、2020年には動画ダウンロードライブラリyoutube-dlが、著作権侵害とされて一時削除されるという騒動が起きました。
清く正しくGitHubを使っていると方であろうと、自分のリポジトリが突然消される心配が絶対にないとは言えないのではないでしょうか。
② GitHubが落ちることもたまにはある
GitHubの障害履歴を見ると、月に数回は小規模な障害が発生しているのが分かります。GitHubで何かがエラーになっているとTwitterが阿鼻叫喚になり、トレンド入りしてしまうのももはや恒例です。
GitHubが全く使えないほどの大きな障害は大変まれではあるものの、万が一必要なときリポジトリにアクセスできなかったら困ってしまいます。
③ PCのローカルリポジトリがバックアップとして万全とは限らない
そもそも、GitHubは分散型バージョン管理ですから、ローカルリポジトリが手元にあればリモート環境がなくても開発できるのが強みです。ただし、バックアップとして考えると万全とは限りません。
ローカルリポジトリを常にリモートと同じに保っておくためには、各リポジトリで定期的にgit pull
し続けなければなりません。さらに、開発用PCであれば、そのリポジトリを編集していてリモートと異なる状態になることもあるでしょう。常にGitHubのリモートリポジトリと同じ状態のデータを保管したい用途には、PCのローカルリポジトリは不向きだと考えられます。
GitHubの自分の全リポジトリをNASに自動バックアップする
ここまで述べた問題を解決するために、GitHubのリポジトリをNASに自動同期するシステムを作りました。
同期先はNASでなくてもOKですが、筆者はPCにある開発用リポジトリとは場所を分けたかったためNASを対象としました。
公開しているコード自体はNASでなくても使えるはずです。
ハードウェア環境
- Synology DS218
- Synology社の2ベイNASです
- 自作のプログラムを簡単に動かせるNASとしては他にQNAP社のものもありますが、本記事の説明はSynologyのNASのものに限定します
NASの外観
ソフトウェア環境
- DSM (Synology NASのOS)
7.0.1
-
Git Server
2.33.0-1016
- 公式で用意されているGitパッケージです
- 主な用途はNASにGitサーバーを建てることですが、今回はクライアントとして使います
- Git LFS
3.1.2
-
Python 3.9
3.9.14-0010
-
Z shell
v5.8-11
- Synology NASのオープンソースコミュニティ"SynoCommunity"が配布する
zsh
です - CLIでの作業を楽にするために使いました
- Synology NASのオープンソースコミュニティ"SynoCommunity"が配布する
- Python
3.9.5
Synology NASでのCLI開発環境構築
SynologyのNASはLinuxをベースにした独自OSです。パソコンではなくNASなのでGUI側でやれることには制約があります。ただし、SSHで直接コマンドを叩けるように設定してしまえば一気にカスタマイズの幅が広がります。
SSHの有効化
「コントロール パネル」→「端末とSNMP」でSSHを有効化します。ポートはデフォルトの22以外に設定することが推奨されています。
ssh {ユーザ名}@{ホスト名 or IPアドレス} -p 22222
のようにssh
コマンドを実行し、ログインパスワードを入力すればSSHでログインできます。
デフォルトシェルは/bin/sh
でした。あまり使い勝手が良くないので、あとでzsh
に変更します。
Git(& Git LFS)のインストール
公式で配布されているGit Serverを「パッケージ センター」からインストールします。
完了したら、SSH経由でコマンドを叩いてGitが使えることを確認します。
$ git --version
git version 2.33.0
もしGit Large File Storage(LFS) を使っているリポジトリがある場合、標準のGitだけではLFS管理下のファイルを取得できません。Git LFSもインストールしましょう。
Synology NASで使えるパッケージマネージャでGit LFSを見つけられなかったため、GitHubのリリースページからビルド済みバイナリを取得して使うことにします。
Git LFSのLinux向けバイナリは"Linux 386(32ビットCPU向け)"、"Linux AMD64(64ビットCPU向け)"、"Linux ARM(ARMの32ビットCPU向け)"などに分かれているため、NASに搭載されたCPUを調べて適合するものを使います。
DS218のCPUはRealtek RTD1296
で、これは64bitのARMアーキテクチャであるため"Linux ARM64"をダウンロードしました(モデルによってCPUは結構違うため、Synologyのページで必ずご確認ください)。
$ wget https://github.com/git-lfs/git-lfs/releases/download/v3.1.2/git-lfs-linux-arm64-v3.1.2.tar.gz
$ echo "c6152c4e24e0575396ee80be8049bf258659fec552f81b410705beed25712ba0 git-lfs-linux-arm64-v3.1.2.tar.gz" | sha256sum -c
git-lfs-linux-arm64-v3.1.2.tar.gz: OK
$ tar -xvf git-lfs-linux-arm64-v3.1.2.tar.gz
$ ./install.sh
$ sudo ./install.sh
Git LFS initialized.
$ git lfs
git-lfs/3.1.2 (GitHub; linux arm64; go 1.17.6)
git lfs <command> [<args>]
git lfs
が動けばOKです。
zshのインストール
シェルの変更は必須でないので飛ばしてもOKですし、bash
やfish
などをお使いなら合わせてよいと思います。
zsh
はSynologyの公式ではなくコミュニティが提供するパッケージの中にあります。おおむね上の解説ページに沿って進めていきます。
「パッケージ センター」で「設定」の「パッケージ ソース」タブを開き、「追加」からhttps://packages.synocommunity.com
を追加します。
すると、パッケージセンターの左メニューに「コミュニティ」ボタンが出てくるので、クリックしてZ shell
を探しインストールします。
終わったらコマンドで入っているか確認します。
$ which zsh
/usr/local/bin/zsh
Synology NASにはログインシェルを変更するchsh
コマンドが無いようなので、代わりに~/.profile
にzsh
を自動で起動する設定を入れます。
if [[ -x /usr/local/bin/zsh ]]; then
export SHELL=/usr/local/bin/zsh
exec /usr/local/bin/zsh
fi
筆者はメインPCから.zshrc
や.vimrc
などのdotfilesを移植して、そこそこ便利に使えるようにしています(わざわざNASで開発する機会は別に多くありませんが……)。
Pythonのインストール
GitHubを巡回してリポジトリを自動同期するには、便利なPythonを使うことにします。
Synologyの公式パッケージにもPythonはあるものの、現在の標準パッケージには古いPython 2しかありません。
2023年現在、公式パッケージとしてPython3.9が存在するのでこれを使えばOKです。ただし、デフォルトではパッケージをインストールするpipコマンドが存在しないため、コマンドでインストールする必要があります。
やり方の記事を別途書きましたので、ぜひご覧ください。
python3.9
、pip3.9
が動いたら完了です。
Python3.9パッケージがなかった頃の古い情報
先駆者の方が「SynologyNAS に最新バージョンの Python 3.8 を入れてみた ~だってパッケージセンターは 3.5 止まりなんだもの~」という記事を書かれていたので、筆者はこれに従って最新のPythonを入れることにしました。
この記事では、Synology NAS用のパッケージマネージャEntware-ngを導入しています。Pythonに限らず様々なパッケージを管理できるので入れて損はありません。
GitHubからリポジトリを自動clone or pullするプログラム
開発環境ができたので、いよいよ本筋のバックアッププログラムを作ります。ソースコード全文はGitHubに置いてあります。
使用するには以下のパッケージが必要です。
-
GitPython
: GitをPythonから操作するパッケージ -
PyGithub
: GitHubをPythonから操作するパッケージ -
python-dotenv
:.env
ファイルから設定を読み込むパッケージ。GitHubトークンなど機密情報をコードに書かずに済みます
自分のGitHubリポジトリのリストを取得する
既に解説記事があるように、PyGithub
パッケージを使ってg.get_user().get_repos(type='owner')
のように実行すると、Github APIからリポジトリ一覧が得られます。
privateリポジトリも確認できるように、 https://github.com/settings/tokensで"repo"スコープを付けたpersonal access tokenを生成しておきましょう。
クローンに必要なrepo.clone_url
だけをリストに抜き出しておきます。
###### prepare github client ######
github_scheme = f'https://{github_token}:x-oauth-basic@' # for cloning private repos
g = Github(github_token)
###### get urls from github (the user and organizations) ######
clone_urls = [repo.clone_url for repo in g.get_user().get_repos(type='owner')]
OrganizationのGitHubリポジトリのリストを取得する
GitHubには、企業やOSSチーム単位でリポジトリを管理できるOrganization機能があります。そこで、Organizationで作ったリポジトリもローカルに同期できるようにします。
ユーザのリポジトリと同様に、g.get_organization({Org名}).get_repos(type='owner')
でリポジトリ一覧を取得できます。
Organization用の設定をrepository_settings.json
に書き、指定したOrganizationから正規表現のパターンに一致するリポジトリのURLを集めるコードを書きました。
with open(os.path.join(os.path.dirname(__file__), 'repository_settings.json')) as f:
repos = json.load(f)
# (中略)
clone_urls_org = set()
for org in repos['orgs']:
for repo in g.get_organization(org['name']).get_repos(type='owner'):
if 'patterns' in org and len(org['patterns']) != 0:
for pattern in org['patterns']:
if re.search(pattern, repo.name) is not None:
clone_urls_org.add(repo.clone_url)
else:
clone_urls_org.add(repo.clone_url)
clone_urls += list(clone_urls_org)
バックアップ先ディレクトリにリポジトリがまだない場合はgit cloneする
ここからはGitPython
でNAS上のGitを操作していきます。
まず、GitHubのURLを加工して{ドメイン名}/{ユーザ名 or Org名}/{リポジトリ名}
の形式にし、これをそのままバックアップ先のディレクトリ構成にします。これは、motemen氏のGitリポジトリ管理ソフトghq
のやり方を参考にしたものです。
###### perform git clone or git pull ######
for url in clone_urls:
parsed_url = urllib.parse.urlparse(os.path.splitext(url)[0])
repo_path = parsed_url.netloc + parsed_url.path # e.g. github.com/github/gitignore
これでclone先のパスを生成できたので、git clone
をリポジトリごとに実行します。しかし、2回目以降で既存のディレクトリにgit clone
しようとするとGitがエラーを出し、Pythonも異常終了してしまいます。
従って、os.path.exists()
でディレクトリがなかったときにだけcloneするようにしました。
###### perform git clone or git pull ######
for url in clone_urls:
# (中略)
if not os.path.exists(f'{clone_dir}{repo_path}'):
try:
git.Repo.clone_from(f'{github_scheme}{repo_path}', f'{clone_dir}{repo_path}', multi_options=['--recursive'])
print(f'[{repo_path}] performed git clone')
except git.exc.GitCommandError as e:
# print(str(e))
print(f'[{repo_path}] clone failed')
各リポジトリでgit pullする
git pull
で各リポジトリを最新状態に保ちます。
次節で述べますが実行後に標準出力をメールで受け取れるので、バックアップができているのを確認できるように新旧のコミットハッシュをprintします。
###### perform git clone or git pull ######
for url in clone_urls:
# (中略)
repo = git.Repo(f'{clone_dir}{repo_path}')
repo.git.checkout('HEAD', '--force')
info = repo.remote().pull()[0]
if info.old_commit is not None:
print(f'[{repo_path}] performed git pull: {info.old_commit}...{info.ref}')
NAS上で定期実行する
プログラム定期実行の定番といえばcronですが、なんとSynology NASのOS(DSM)にはGUIで設定できるタスクスケジューラがあります。
「コントロール パネル→タスク スケジューラ」を開き、「作成→予約タスク→ユーザー指定のスクリプト」と進みます。
「スケジュール」タブで実行する時間を曜日・時刻で設定できます。筆者は毎日指定時刻に実行されるようにしています。
「タスク設定」タブでは、実行するシェルスクリプトとメール通知を設定します。Pythonやスクリプトの場所は絶対パスで書いてあげたほうが確実に動くと思います。
/opt/bin/python3 /{絶対パス}/backup_github/backup_github.py
Eメール通知にチェックを入れておくと、標準出力の内容が指定したメールアドレスに届いて便利です。
設定
届くメールの例
現時点で対応していないこと
- GitHub Wikiのバックアップ
- Wikiは別のリポジトリとしてgitから操作できます
- Wikiになくしたくない情報がある方の場合は、Wikiのリポジトリも同様に自動バックアップする機能が必要になるでしょう
Discussion