ruby + plist でNASのTimeMachine用sparsebundleファイルを常にマウントしておく

最終形
--

<img width="370" alt="notification.png" src="https://qiita-image-store.s3.amazonaws.com/0/16757/720696b7-6a97-6274-61b4-f9155890dbbe.png">


問題
--

[Mac覚書 TimeMachineでNASを使用する方法](http://qiita.com/team8ware/items/af6a8f254873d7cc4ef9)
NAS上にTimeMachineファイルを置いてTimeMachineを使っても、`起動時`、`Sleep後`はsparsebundleファイルをマウントし直さないとTimeMachineが機能しない。

解決策
--

samba経由でnasのフォルダをマウントして、samba上のsparsebundleファイルを自動でマウントするようにする。

1. rubyで書きたい
2. plistで自動起動させたい

方法
--

まず、samba経由で目的のNASフォルダをマウントする

```bash
#マウント先フォルダを作って
mkdir /Volumes/your_mount_name
#sambaフォルダをマウント nasにログインできる「guest:」か「login:pass」で
mount_smbfs //guest:@your_nas_name/your_path_name /Volumes/your_mount_name
```

マウントしたフォルダ内にあるsparsebundleファイルをさらにマウントする

```bash
hdiutil attach -mountpoint /Volumes/your_timemachine_mount_name /Volumes/your_mount_name/your_timemachine.sparsebundle
```

これをrubyにまとめて引数で指定できるようしたうえで、さらにosxの`notification`で表示できるように`terminal-notifier`をインストール

```bash
brew install terminal-notifier
```

<img width="370" alt="notification.png" src="https://qiita-image-store.s3.amazonaws.com/0/16757/720696b7-6a97-6274-61b4-f9155890dbbe.png">


まとめたものがこれ

### rubyファイル


```rb
# timemachine.drive.rb

require "pathname"
require "shellwords"

class AutoTimeMachineDrive
  class << self
    def mount!
      new.mount_timemachine_drive
    rescue => ex
      notify(ex.message)
    end

    def notify(message)
      options = {
        group:   self.to_s,
        title:   self.to_s,
        message: message,
        sound:   :default
      }

      system "/usr/local/bin/terminal-notifier #{option_line options}"
    end

    def option_line(*options)
      options.collect do |element|
        case element
        when Hash
          element.collect{|k, v| "-#{k} #{Shellwords.escape v}"}.join " "
        when String, Pathname
          element.to_s
        end
      end.join " "
    end
  end

  def initialize
    @smb_url, @smb_mount_name,
    @time_machine_mount_name, @time_machine_sparsebundle_file_path = ARGV
  end

  def mount_timemachine_drive
    return if tm_mount_point.exist?

    ensure_smb_mount_point do
      ensure_tm_mount_point_is_missing do
        mount_timemachine_sparsebundle_file
        self.class.notify("#{time_machine_mount_name} mounted.") if tm_mount_point.exist?
      end
    end
  end

  private
    attr_reader :smb_url, :smb_mount_name,
                :time_machine_mount_name, :time_machine_sparsebundle_file_path

    def smb_mount_point
      Pathname "/Volumes/#{smb_mount_name}"
    end

    def tm_mount_point
      Pathname "/Volumes/#{time_machine_mount_name}"
    end

    def ensure_smb_mount_point
      prepare_smb_mount_point do
        mount_samba
        yield
      end
    end

    def ensure_tm_mount_point_is_missing
      yield unless tm_mount_point.exist?
    end

    def prepare_smb_mount_point
      if smb_mount_point.exist?
        if [smb_mount_point_not_network_drive?, smb_mount_point_not_empty?].all?
          return self.class.notify "unempty smb_mount_point (#{smb_mount_point}) exists. make sure this directory is removed before running."
        end
      else
        smb_mount_point.mkdir
      end

      yield
    end

    def smb_mount_point_not_network_drive?
      `mount`.lines.select{|line| line.include? smb_mount_point.to_s }.count == 0
    end

    def smb_mount_point_is_network_drive?
      not smb_mount_point_not_network_drive?
    end

    def smb_mount_point_not_empty?
      smb_mount_point.children.count > 0
    end

    def mount_samba
      return if smb_mount_point_is_network_drive?
      system "mount_smbfs #{self.class.option_line(smb_url, smb_mount_point)}"
    end

    def mount_timemachine_sparsebundle_file
      system "hdiutil attach #{self.class.option_line(
          {mountpoint: tm_mount_point},
          time_machine_sparsebundle_file_path
        )}"
    end

end


AutoTimeMachineDrive.mount!
```

このファイルを`timemachine.drive.rb`などとして`~/bin`などに入れて、次にplistを作る

### plistファイル

```xml
<!--  local.timemachine.drive.plist  -->

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>local.timemachine.drive</string>
	<key>ProgramArguments</key>
	<array>
		<string>ruby</string>
        <!--rbファイルのフルパス-->
		<string>/Users/your_name/bin/timemachine.drive.rb</string>
        <!--ログインできるNASのURL-->
		<string>//guest:@your_nas_name/your_path_name</string>
        <!--sambaフォルダのマウント名 /Volumes配下に自動マウント-->
		<string>your_mount_name</string>
        <!--sparsebundleマウント時の表示ドライブ名-->
		<string>your_timemachine_mount_name</string>
        <!--samba経由で参照できるsparsebundleフルパス(/Volumes配下経由)-->
		<string>/Volumes/your_mount_name/your_timemachine.sparsebundle</string>
	</array>
	<key>RunAtLoad</key>
	<true/>
	<key>StartInterval</key>
	<integer>60</integer>
</dict>
</plist>

```

### launchctlのロード

`local.timemachine.drive.plist`などとしたこのplistを`~/Library/LaunchAgents/`に置いて

```bash
launchctl load ~/Library/LaunchAgents/local.timemachine.drive.plist
```

このコマンド一発で自動にTimeMachineドライブをマウントします。