Featured image of post CachyOS(Arch Linux)とMacでファイル共有環境を構築する — Btrfs + Samba + Tailscale + GCSバックアップ

CachyOS(Arch Linux)とMacでファイル共有環境を構築する — Btrfs + Samba + Tailscale + GCSバックアップ

このブログ記事は、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から接続:

  1. Cmd + K でサーバ接続ダイアログを開く
  2. 自宅LAN: smb://<LOCAL_IP>/shared
  3. 外出先: smb://<TAILSCALE_IP>/shared
  4. ユーザー名と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から接続

  1. Remote - SSH 拡張機能をインストール
  2. Cmd + Shift + P → “Remote-SSH: Connect to Host”
  3. cachyos-lan または cachyos-ts を選択
  4. 接続後、「フォルダを開く」で /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/configDynamicForward 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-browserredirect_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 自動実行
Hugo で構築されています。
テーマ StackJimmy によって設計されています。