このブログ記事は、Claud.aiと槍とした内容を整理して作成しています
はじめに
自宅のデスクトップPC(CachyOS / Arch Linux系)のストレージをMacBookから共有して作業したいと思いました。さらに、ストレージ障害に備えてGoogle Cloud Storage(GCS)への自動バックアップも構築します。
本記事では、以下の構成を一から構築した手順を詳細に記録しています。
最終構成図
Mac (Finder / VSCode)
├── 自宅LAN: smb://<LOCAL_IP>/shared (高速・LAN直結)
├── 外出先: smb://<TAILSCALE_IP>/shared (Tailscale VPN経由)
└── VSCode: SSH経由 → /mnt/shared(Remote開発)
↓
CachyOS (デスクトップPC)
├── /mnt/shared(Btrfs @shared サブボリューム)
├── snapper(自動スナップショット)
├── smartd(ディスク監視)
└── 毎日AM3:00 → GCS自動バックアップ
↓
GCS: gs://<BUCKET_NAME>/shared/
├── バージョニング有効(誤上書き対策)
└── ライフサイクルルール(90日で旧バージョン自動削除)
障害対策カバレッジ
| リスク | 対策 |
|---|---|
| ファイル誤削除・誤操作 | snapperスナップショットで即復元 |
| ファイル誤上書き | GCSバージョニングで旧版復元 |
| ディスク故障 | GCSへの日次バックアップ |
| 故障予兆 | smartdで常時監視 |
| GCSコスト肥大 | ライフサイクルルールで90日超の旧版自動削除 |
環境
- CachyOS側: NVMe SSD 1TB(Btrfs)、AMD Ryzen
- Mac側: MacBook(macOS、Tailscale導入済み)
- ネットワーク: 自宅LAN 192.168.x.0/24、Tailscale VPN導入済み
- GCP: 既存プロジェクトを使用
1. Btrfsサブボリューム作成と共有フォルダの準備
1-1. 現状確認
CachyOSはインストール時にBtrfsを選択しており、既にサブボリューム構成が整っていました。
lsblk -f
nvme0n1
├─nvme0n1p1 vfat FAT32 /boot
└─nvme0n1p2 btrfs / /home /root /srv /var/log /var/cache /var/tmp
約950GBのNVMe SSDにBtrfsが構成済みです。
既存サブボリューム構成を確認します:
sudo btrfs subvolume list /
ID 256 gen xxxxx top level 5 path @
ID 257 gen xxxxx top level 5 path @home
ID 258 gen xxxxx top level 5 path @root
ID 259 gen xxxxx top level 5 path @srv
ID 260 gen xxxxx top level 5 path @cache
ID 261 gen xxxxx top level 5 path @tmp
ID 262 gen xxxxx top level 5 path @log
CachyOSの典型的な構成で、snapperによるルートのスナップショットも既に運用されていました。
1-2. 共有用サブボリューム @shared の作成
トップレベル(ID 5)にマウントしてサブボリュームを作成します。
# トップレベルにマウント
sudo mount -o subvolid=5 /dev/nvme0n1p2 /mnt
# 既存サブボリューム確認
ls /mnt
# @ @cache @home @log @root @srv @tmp
# 共有用サブボリューム作成
sudo btrfs subvolume create /mnt/@shared
# 確認
sudo btrfs subvolume list /mnt | grep shared
# トップレベルのマウント解除
sudo umount /mnt
1-3. マウントと永続化
# マウントポイント作成
sudo mkdir -p /mnt/shared
# マウント(compress=zstdでテキスト系ファイルを自動圧縮)
sudo mount -o subvol=@shared,noatime,compress=zstd /dev/nvme0n1p2 /mnt/shared
# 所有者を作業ユーザーに変更
sudo chown <USERNAME>:<USERNAME> /mnt/shared
/etc/fstab に追記して再起動後も自動マウントされるようにします。既存エントリのオプションに合わせました。
UUID=<YOUR_UUID> /mnt/shared btrfs subvol=/@shared,defaults,noatime,compress=zstd:1 0 0
# fstab反映
sudo systemctl daemon-reload
sudo mount -a
# 確認
df -hT /mnt/shared
1-4. snapperによるスナップショット設定
snapperはCachyOSに既にインストール済みだったため、共有領域用の設定を追加するだけで済みました。
# 共有領域の設定作成
sudo snapper -c shared create-config /mnt/shared
# 自動スナップショットの保持数を設定
sudo snapper -c shared set-config TIMELINE_LIMIT_HOURLY=10
sudo snapper -c shared set-config TIMELINE_LIMIT_DAILY=7
sudo snapper -c shared set-config TIMELINE_LIMIT_MONTHLY=3
sudo snapper -c shared set-config TIMELINE_CREATE=yes
# タイマー有効化
sudo systemctl enable --now snapper-timeline.timer
sudo systemctl enable --now snapper-cleanup.timer
# 動作確認
sudo snapper -c shared create --description "initial"
sudo snapper -c shared list
これで時間ごと10個、日ごと7個、月ごと3個のスナップショットが自動で保持されます。ファイルを誤削除した場合は snapper -c shared undochange で復元できます。
2. Samba + Tailscale によるファイル共有
2-1. 設計方針
- 自宅LAN内: ローカルIPで高速アクセス
- 外出先: Tailscale VPN経由で安全にアクセス
- アクセス制限: Tailscale CGNAT範囲と自宅LANのみ許可
2-2. Sambaインストールと設定
sudo pacman -S samba
/etc/samba/smb.conf を作成します:
[global]
workgroup = WORKGROUP
server string = CachyOS
security = user
map to guest = Never
# Tailscale CGNAT + 自宅LANのみ許可
hosts allow = 100.64.0.0/10 192.168.x.0/24
hosts deny = ALL
# 日本語対応
unix charset = UTF-8
dos charset = CP932
server min protocol = SMB3
ea support = yes
[shared]
path = /mnt/shared
browseable = yes
writable = yes
valid users = <USERNAME>
create mask = 0644
directory mask = 0755
ハマりポイント:bind interfaces only + tailscale0 は動かない
当初、interfaces = tailscale0 および bind interfaces only = yes を設定しましたが、SambaがTailscaleのTUNインターフェースを認識できず no network interfaces found でクラッシュしました。
WARNING: no network interfaces found
open_sockets_smbd: No sockets available to bind to.
INTERNAL ERROR: open_sockets_smbd() failed
interfaces = <TAILSCALE_IP>/32 のようにIPアドレスを直指定しても、ソケットのバインドに失敗しました。
最終的に、interfaces / bind interfaces only を使わず、全インターフェースでリッスンし hosts allow / hosts deny でアクセス制御する方式に変更して解決しました。
2-3. Sambaユーザー設定と起動
# Sambaパスワード設定
sudo smbpasswd -a <USERNAME>
# サービス起動(nmbはTailscale環境では不要)
sudo systemctl enable --now smb
nmbサービス(NetBIOS名前解決)はTailscale経由のIP直指定では不要なため、有効化しません。当環境ではnmbの起動自体がタイムアウトで失敗していたため、無効化しました。
2-4. UFWファイアウォール設定
CachyOSではUFWが有効になっており、デフォルトでSambaのポート(445/139)がブロックされていました。自宅LANからのみ許可します。
sudo ufw allow from 192.168.x.0/24 to any port 445
sudo ufw allow from 192.168.x.0/24 to any port 139
Tailscale経由のアクセスはTailscaleが独自にトンネリングするため、UFWのルール追加なしで通過します。
2-5. Mac側からの接続
Finderから接続:
Cmd + Kでサーバ接続ダイアログを開く- 自宅LAN:
smb://<LOCAL_IP>/shared - 外出先:
smb://<TAILSCALE_IP>/shared - ユーザー名とSambaパスワードを入力
ターミナルから確認:
smbutil view //<USERNAME>@<LOCAL_IP>
2-6. 日本語ファイル名のテスト
# Mac側で日本語ファイル名のファイルを作成
echo "テスト" > /Volumes/shared/日本語テスト.txt
# CachyOS側で確認
cat /mnt/shared/日本語テスト.txt
問題なく読み書きできました。ただし、macOSとLinuxではUTF-8のNFC/NFD正規化方式が異なるため、常にLinux側(/mnt/shared)をマスターとし、Mac側からはSamba経由でアクセスする運用にすることでNFC/NFD混在を防ぎます。
3. Google Cloud Storage への自動バックアップ
3-1. なぜGCSか(Google Driveとの比較)
| 観点 | GCS | Google Drive |
|---|---|---|
| ファイル名の扱い | バイト列そのまま、NFC統一しやすい | 内部で正規化される可能性あり |
| パーミッション保持 | tar.gzで保持可能 | 失われる |
| 自動化 | gcloud CLI で容易 | API経由で煩雑 |
| バージョニング | バケット単位で明確に管理 | 制御しにくい |
| 大量の小ファイル | 得意 | 同期が重くなる |
| コスト | Nearlineで$0.01/GB/月 | Workspace容量に含まれる |
ソースコードや設定ファイルが中心の場合、GCSの方が明確に向いています。
3-2. gcloud CLIインストール
CachyOS(Arch系)ではAURからインストールします。
paru -S google-cloud-cli
SSH経由の場合、ブラウザが開けないので --no-launch-browser オプションで認証します。
gcloud auth login --no-launch-browser
表示されるURLをMac側のブラウザで開き、認証コードをターミナルに貼り付けます。
なお、--no-browser オプションでは redirect_uri 関連のエラー(400: 無効なリクエスト)が発生したため、--no-launch-browser を使用しました。
gcloud config set project <YOUR_PROJECT_ID>
3-3. GCSバケット作成
# 東京リージョン、Nearlineストレージクラスで作成
gcloud storage buckets create gs://<BUCKET_NAME> \
--location=asia-northeast1 \
--default-storage-class=nearline
# バージョニング有効化(誤上書き対策)
gcloud storage buckets update gs://<BUCKET_NAME> --versioning
ハマりポイント:gsutilが入っていない
AURの google-cloud-cli パッケージには gsutil が含まれていませんでした。代わりに gcloud storage コマンドを使います。gcloud storage は新しいgcloud CLIに組み込まれており、gsutilと同等の機能を持っています。
3-4. バックアップスクリプト作成
#!/bin/bash
# ~/backup-to-gcs.sh
LOG="/var/log/gcs-backup.log"
echo "$(date): Backup started" >> "$LOG"
gcloud storage rsync -r --delete-unmatched-destination-objects \
/mnt/shared gs://<BUCKET_NAME>/shared/ \
>> "$LOG" 2>&1
echo "$(date): Backup finished (exit: $?)" >> "$LOG"
chmod +x ~/backup-to-gcs.sh
ログファイルの権限設定を忘れずに行います:
sudo touch /var/log/gcs-backup.log
sudo chown <USERNAME>:<USERNAME> /var/log/gcs-backup.log
3-5. systemd timerで日次自動化
# /etc/systemd/system/gcs-backup.service
[Unit]
Description=Backup shared to GCS
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/home/<USERNAME>/backup-to-gcs.sh
User=<USERNAME>
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/gcs-backup.timer
[Unit]
Description=Daily GCS backup
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now gcs-backup.timer
3-6. 動作確認
echo "backup test" > /mnt/shared/backup-test.txt
bash ~/backup-to-gcs.sh
gcloud storage ls gs://<BUCKET_NAME>/shared/
gs://<BUCKET_NAME>/shared/backup-test.txt
gs://<BUCKET_NAME>/shared/test-mac.txt
gs://<BUCKET_NAME>/shared/日本語テスト.txt
日本語ファイル名も含め、正常にバックアップされました。
3-7. ライフサイクルルール設定
バージョニングで蓄積される旧バージョンを90日後に自動削除し、コストを抑制します。
{
"rule": [
{
"action": {"type": "Delete"},
"condition": {
"daysSinceNoncurrentTime": 90,
"isLive": false
}
}
]
}
gcloud storage buckets update gs://<BUCKET_NAME> \
--lifecycle-file=/tmp/lifecycle.json
3-8. 障害時の復元方法
バックアップは復元できなければ意味がありません。状況に応じた復元手順をまとめます。
snapperスナップショットからの復元(誤削除・誤操作の直後)
snapperは時間ごとにスナップショットを取得しているため、直近の誤操作であればすぐに復元できます。
# スナップショット一覧を確認
sudo snapper -c shared list
# スナップショット間の差分を確認(例: スナップショット1と現在の差分)
sudo snapper -c shared status 1..0
# 特定のスナップショットから現在の状態へ変更を取り消す
sudo snapper -c shared undochange 1..0
# 特定のファイルだけ復元したい場合は、スナップショットから直接コピー
cp /mnt/shared/.snapshots/1/snapshot/path/to/file.txt /mnt/shared/path/to/file.txt
GCSからの復元(数日後に気づいた場合)
日次バックアップはバージョニングが有効なため、削除・上書きされたファイルも90日以内であれば復元できます。
# 特定ファイルの旧バージョン一覧を確認
gcloud storage ls -a gs://<BUCKET_NAME>/shared/path/to/file.txt
# 出力例:
# gs://<BUCKET_NAME>/shared/path/to/file.txt#1708012800000000
# gs://<BUCKET_NAME>/shared/path/to/file.txt#1708099200000000
# 特定バージョンを復元(#以降がバージョン番号)
gcloud storage cp \
"gs://<BUCKET_NAME>/shared/path/to/file.txt#1708012800000000" \
/mnt/shared/path/to/file.txt
ディスク全損時の復元
ディスクが完全に故障した場合は、GCSからまるごと復元します。
# 新しいディスクに@sharedサブボリュームを再作成した後
gcloud storage rsync -r \
gs://<BUCKET_NAME>/shared/ /mnt/shared/
復元方法の早見表
| 状況 | 復元方法 | 復元可能な期間 |
|---|---|---|
| 誤削除・誤操作(直後〜数時間) | snapperスナップショット | 時間ごと10個、日ごと7個、月ごと3個 |
| 誤削除・誤上書き(数日後に気づいた) | GCSバージョニング | 90日以内 |
| ディスク全損 | GCSからフルリストア | 最終バックアップ時点 |
4. ディスク監視(smartmontools)
sudo pacman -S smartmontools
sudo systemctl enable --now smartd
sudo smartctl -H /dev/nvme0n1
# SMART overall-health self-assessment test result: PASSED
主要な値:
| 項目 | 値 |
|---|---|
| Available Spare | 100% |
| Percentage Used | 1% |
| Temperature | 25℃ |
| Media and Data Integrity Errors | 0 |
smartdが常駐して異常検知時に通知してくれます。
5. VSCode Remote SSH によるリモート開発
Samba経由のファイル共有に加えて、VSCode Remote SSHで直接CachyOS上の /mnt/shared を開いて開発することもできます。Sambaマウントよりもファイル操作が高速で、ターミナルもそのままCachyOS上で動くため、開発作業にはこちらの方が快適です。
5-1. CachyOS側のSSH設定
SSHDが動いていることを確認します。
sudo systemctl enable --now sshd
UFWでポート22は既に許可済みでした。
5-2. Mac側のSSH config
~/.ssh/config に接続先を追加します。
Host cachyos-lan
HostName <LOCAL_IP>
User <USERNAME>
Host cachyos-ts
HostName <TAILSCALE_IP>
User <USERNAME>
SSH鍵認証を設定しておくとパスワード入力が不要になります。
# Mac側で鍵生成(既にあればスキップ)
ssh-keygen -t ed25519
# CachyOSに公開鍵を転送
ssh-copy-id <USERNAME>@<LOCAL_IP>
5-3. VSCodeから接続
- Remote - SSH 拡張機能をインストール
Cmd + Shift + P→ “Remote-SSH: Connect to Host”cachyos-lanまたはcachyos-tsを選択- 接続後、「フォルダを開く」で
/mnt/sharedを選択
5-4. ハマりポイント:fishシェルだとVSCode Remote SSHがタイムアウトする
CachyOSのデフォルトシェルがfishの場合、VSCode Remote SSHの接続がタイムアウトで失敗することがあります。SSHの認証自体は成功しているのに、その後のVSCode Serverのセットアップ段階で止まってしまう現象です。
ログには以下のように表示されます:
Authenticated to <LOCAL_IP> using "publickey".
...
Resolver error: Error: Connecting with SSH timed out
VSCode Remote SSHは接続時に動的ポートフォワーディング(SOCKSプロキシ)を使用しますが、fishシェルとの組み合わせで問題が発生するようです。
以下を試しましたが解決しませんでした:
- VSCode設定で
remote.SSH.enableDynamicForwardingを無効化 ~/.ssh/configにDynamicForward noneを追加
解決策:ログインシェルをbashに変更する
# CachyOS側で
chsh -s /bin/bash
sudo systemctl restart sshd
これでVSCode Remote SSHの接続が成功するようになりました。
5-5. fishシェルを引き続き使う
ログインシェルをbashに変更しても、通常のSSH接続やターミナルではfishを使いたい場合があります。~/.bashrc の末尾に以下を追記することで、VSCode以外のセッションではfishが自動起動するようになります。
# ~/.bashrc の末尾に追記
if [[ -z "$VSCODE_INJECTION" ]] && [[ $(ps --no-header -o comm $PPID) != "sshd" || -z "$SSH_CONNECTION" ]]; then
exec fish
fi
この分岐により:
- VSCode Remote SSH → bash のまま(
$VSCODE_INJECTIONが設定されるため) - 通常のターミナル → fishが自動起動
と使い分けることができます。
6. 運用上の注意点
NFC/NFD問題への対処
macOSはUTF-8 NFD(「が」=「か」+「゛」)、LinuxはUTF-8 NFC(「が」= 1文字)を使用します。CachyOS側のストレージをマスターとし、Mac側からはSamba経由のみでアクセスする運用にすることでNFC統一を維持します。
もし混在した場合は convmv で正規化できます:
sudo pacman -S convmv
convmv -r -f utf8 --nfc -t utf8 /mnt/shared --notest
fishシェルでのheredoc
CachyOSのデフォルトシェルがfishの場合、bashのheredoc(<< 'EOF')が使えません。bash -c '...' でラップするか、sudo tee を使ってください。
gcloud auth のSSH経由での認証
SSH経由では gcloud auth login --no-browser が redirect_uri エラーで失敗することがあります。--no-launch-browser オプションを使うことで解決しました。
GCSバックアップで失われるもの
ローカルファイルシステムからGCSにバックアップする際、以下は保持されません:
- Unixパーミッション(chmod)
- 所有者・グループ(uid/gid)
- シンボリックリンク
- 拡張属性(xattr)
パーミッション保持が必要な場合は、tar.gzに固めてからアップロードする方法もあります:
tar czpf - /mnt/shared | gcloud storage cp - gs://<BUCKET_NAME>/archive-$(date +%Y%m%d).tar.gz
まとめ
ディスク1本のデスクトップPCでも、Btrfsのスナップショット + GCSバックアップの組み合わせにより、誤操作からディスク障害まで幅広いリスクに対応できる構成を実現しました。
Samba + Tailscaleにより、自宅ではLAN直結で高速アクセス、外出先でもVPN経由で安全にアクセスでき、MacとLinux間のシームレスなファイル共有が可能になりました。さらにVSCode Remote SSHを組み合わせることで、MacからCachyOS上のファイルを直接編集する快適な開発環境が整いました。
最終的な接続方法の一覧です:
| 用途 | 接続方法 |
|---|---|
| Finderでファイル操作(自宅) | smb://<LOCAL_IP>/shared |
| Finderでファイル操作(外出先) | smb://<TAILSCALE_IP>/shared |
| VSCode Remote開発 | SSH → /mnt/shared |
| GCSバックアップ | 毎日AM3:00 自動実行 |