ZFS/FreeBSD上でTimeMachineを動かした

ZFS上でsambaが動いているFreeBSDサーバを、TimeMachineサーバにする事ができたのでメモを残す事にする。

なお調査の過程で分かった事を以下のページにまとめてある。

https://qiita.com/ftns/items/47a60e918a1cdd49fa9d

| ※ 10/30/2024 追記 |
|:-|
| FreeBSD 13.3-RELEASE-p7 と samba 4.16.11_5 (samba416), macOS Sonoma 14.6.1 ではこの設定で動作しているが、macOS Sequoia 15.1ではエラーとなる。  |

# 調査環境
* Server:
  - OS: 12.2-RELEASE-p11
  - samba: 4.13.14(ports)
* Client 1: MacBook (Retina 12inch Early 2016)
 Mojave(10.14.6)
* Client 2: MacBook Pro（M1Max 14inch 2021) Monterey (12.1)
* Client 3: MacBook Air (M1 2020) Big Sur (11.6.2)

# 設定

## zfs

#### 拡張属性(xattr,EA)[^1]

FreeBSD 12以前では、ZFSで拡張属性はサポートされていない事になっている。しかし実際には少なくともsambaでTimeMachineを実現できるレベルでは使える。

FreeBSD 12以前では、`options UFS_EXTATTR`を追加して[カーネル再構築](https://docs.freebsd.org/ja/books/handbook/kernelconfig/ "FreeBSDハンドブック 第8章 FreeBSD カーネルのコンフィグレーション")する[^2]。

[^1]: 拡張属性(extended attributes)は、xattr 又は EA と略されるようである。
[^2]: ZFSで完結すれば不要かもしれないが、未調査。


#### ACL
現状では**MacとSambaの間でACLは共有されない。**
TimeMachineを使う上でACLは不要だと思われるが、念のため設定しておく。

```shell:
# zfsへの設定 
$ sudo zfs set aclmode=passthrough <pool>/<filesystem>
$ sudo zfs set aclinherit=passthrough <pool>/<filesystem>
```

## samba

#### インストール

```shell:
$ sudo pkg install samba413
```

なお`pkg`については、[FreeBSDハンドブック](https://docs.freebsd.org/ja/books/handbook/ports/#pkgng-intro "4.4. pkg によるバイナリ package の管理")を参照の事。

#### smb4.conf
以下は抜粋である。

```conf:smb4.conf
[global]
    workgroup = WORKGROUP
    security = USER
    server min protocol = SMB2
    server string = <your server>
    server role = standalone server
    dos filemode = yes
    store dos attributes = yes
    map archive = no
    unix extensions = no
    
    # veto files
    delete veto files = yes
    veto files = /._*/.DS_Store/

    # ACL
    force unknown acl user = yes
    inherit acls = yes
    map acl inherit = yes

    # vfs
    vfs objects = zfsacl catia fruit streams_xattr full_audit

    # vfs_zfsacl
    nfs4:chown = yes

    # vfs_fruit
    fruit:zero_file_id = yes
    fruit:metadata = stream
    fruit:veto_appledouble = no
    fruit:resource = xattr
    fruit:model = PowerMac

    # vfs_streams_xattr
    streams_xattr:store_stream_type = no
    streams_xattr:prefix = user.

[TimeMachine]
    comment = TimeMachine
    path = <somewhere on ZFS>
    read only = no
    valid users = <yourname>
    fruit:time machine max size = <volume size>
    fruit:time machine = yes
    browseable = no
    writable = yes
```

## avahi
TimeMachineを使うには、avahiが必要である。pkgでsambaをインストールしていれば、`avahi-app`が既にインストールされている。

設定は以下の通り。

```conf:/usr/local/etc/avahi/services/smb.service
<?xml version="1.0" standalone='no'?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
    <name replace-wildcards="yes">%h</name>
    <service>
        <type>_smb._tcp</type>
        <port>445</port>
    </service>
</service-group>
```

```conf:/usr/local/etc/avahi/services/hosts
192.168.xx.xxx    <サーバ名>.local
```

## daemon起動

以下の3行を`/etc/rc.conf`に追加

```conf:/etc/rc.conf
avahi_daemon_enable="YES"
nmbd_enable="YES"
samba_server_enable="YES"
```

起動するには、以下を実行

```shell:
$ sudo service avahi-daemon start
$ sudo service samba_server start
```

# tips

## 複数のユーザでTimeMachineを共有する場合

TimeMachineでは一つのボリューム内に複数のmacのバックアップイメージが作られる[^5]ので、複数人で一つのTimeMachineボリュームを使う場合は工夫が必要である。

筆者は以下の方法で対処した。

```conf:smb4.conf
[TimeMachine]
    path = <somewhere>
    read only = no
    browseable = no
    writable = yes
    fruit:time machine max size = <volume size>
    fruit:time machine = yes

    valid users = <user 1>, <user 2>, ...
    force user = tmbackup
    force group = tmbackup
```
`valid users`で使用するユーザを指定する。
サーバ側OSで擬似ユーザ（ここでは`tmbackup`）を作成し、TimeMachineディレクトリの所有者をその擬似ユーザとする。
ユーザ間でTimeMachineボリュームが見えてしまう問題は、暗号化で対処する。

なお一つのsambaサーバで複数のTimeMachineボリュームを作る方法を以下のページにまとめてある。

https://qiita.com/ftns/items/d74f930bc4bcf99af780

## sambaのアイコン
MacのFinderで表示されるアイコンは`smb4.conf`の`fruit:model`で設定するが、アイコンの実体はMac上に存在する。
ファイルとしては、`/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/*.Icns`に存在する。

`fruit:model`で指定するキーとアイコンファイルの割り当ては、、
`/System/Library/CoreServices/CoreTypes.bundle/Contents/Info.plist` 内にある。

```shell:
$ plutil -p Info.plist
```

でhuman readableにした上で、`com.apple.device-model-code`をサーチすると見つかる。

例：

| key | icon |
|:---|:---|
|Xserve|ラックマウント型サーバ|
|MacPro6,1|円筒形MacPro(2016)|
|PowerMac|PowerMac G4 Graphite(1999-2001)|
|Macmini|MacMini(2018)|

# 問題点

## アンチウイルスソフトとの競合

M1 MacBook Air(2020) Big Sur 11.6.2（[調査環境](#調査環境)のClient 3）でTimeMachineを実行すると極端に動作が重くなり、勝手にリブート（kernel panic？）を起こす事もあると言う現象に遭遇した。
CPU負荷やメモリ消費は問題なさそうだったので、ディスクI/Oが原因であろうと推測した。ディスクI/Oで最初に疑わしいのはアンチウィルスである。
[ESETのサポート情報](https://eset-support.canon-its.jp/faq/show/17398)を参考にして、以下の2つをスキャナの除外に登録した所、問題なく動いているように見える。

```
/System/Applications/Time Machine.app/*.*
/Volumes/.timemachine/*.*
```

なお[調査環境](#調査環境)のClient 1, 2ではこのような設定は必要なかった。

:::note warn
ここでESET CyberSecurity Proを取り上げたのは、たまたま筆者が使っているからである。同製品に問題があるとは思わないし、推奨するものでも無い。なお他製品でも同種の問題があり、似たような方法で対処可能であるようだ。
:::

## Spotlightとの競合
TimeMachineバックアップ中、バックアップボリュームの内容に対してMac上のSpotlight（`mds`）がインデックスを作っている。
大抵の場合バックアップよりもインデックス作成の方が時間がかかるので、バックアップ終了時にはまだインデックスを作成中である。

ここで問題なのが、Mojave(10.14)では**バックアップ終了後直ぐにバックアップボリュームをumountしようとして失敗し、そのまま放置**されてしまうという物である。この為バックアップボリュームのファイルシステムがdirtyのまま放置される事があり、次のTimeMachineバックアップで恐怖の「〜上のバックアップの検証を完了しました。信頼性を向上するために、新規バックアップを作成する必要があります。」が出てしまい、一からバックアップをやり直す必要に迫られる。当然過去の履歴も失われてしまう。

この問題を根本的に解決するには、Mojaveの使用を止めてCatalina以降へバージョンアップするしか無い[^3]と思われる。ただし32bitアプリケーションを使っているなど、どうしてもその方法が取れない事もあり得るだろう。
筆者はTimeMachine環境設定の「バックアップを自動作成」を外し、以下のようなスクリプト（`tm_workaround.sh`）をcronで1時間に1回動かす[^4]事で一時凌ぎとした。
本スクリプトはSpotlight index作成が終わるまでネットワークが切断されない事を前提としている。運頼みなのでこれで壊れなくなる訳ではないが、無いよりはマシである。

[^3]: この問題がCatalinaやBig Sur, Montereyで解決していると言う根拠は見つけられなかった。何らかの対策は取られているだろうが、ログからは読み取れなかった。Montereyのログによれば、Spotlightが終了するまではumountを待っていると言うところまでは確認できた。ただしシステム環境設定のTime Machine環境設定では、バックアップが終了した所で表示も終了してしまい、Spotlight index作成が続いている事は読み取れない。
[^4]: `crontab -e`がエラーになる場合は、[こちら](https://qiita.com/labocho/items/e1d8ae8223f60ad9d4ee)を参照。

<details><summary>`tm_workaround.sh`</summary><div>

```shell:tm_workaround.sh
#!/bin/bash
# Workaround for TimeMachine on Mojave

isAC(){
  pmset -g ac | awk '/No adapter attached/ { exit 1 }'
}

if [ -f $HOME/.tm_running ]; then
  exit 0
fi

if ! isAC; then
  exit 0
fi

touch $HOME/.tm_running >/dev/null 2>&1

tmutil startbackup --block >/dev/null 2>&1
while [ -d /Volumes/Time\ Machineバックアップ ]; do
  sleep 60
  diskutil umount /Volumes/Time\ Machineバックアップ >/dev/null 2>&1
done
if [ -d /Volumes/com.apple.TimeMachine.<TMvolname>-* ]; then
  diskutil umount force /Volumes/com.apple.TimeMachine.<TMvolname>-* >/dev/null 2>&1
  # <TMvolname>はTimeMachineのボリューム名に置き換える
fi

rm $HOME/.tm_running
```
</div></details>