Command+hを別の機能に割り当てる(Ghosttyの場合)

MacのCommand+hは画面を隠すというショートカットですが、まあまず使わないです。 そして、Command+hjklでターミナルの分割パネルを移動するようにするためには一手間掛かります。

Macの設定

残念ながら無効には出来なくて別のキーバインドに変更します。

discussionsjapan.apple.com

この記事にあるようにアプリ毎に設定が必要です。 たいていのアプリは、メニューに出ている隠す機能の名前が違うからです。

今回は新しく入れたGhosttyの設定をします。

「システム設定」>「キーボード」>「キーボードショートカット」>「アプリのショートカット」から「+」を押して、以下のようにアプリを選択し、メニューに出ている隠す機能の名前と、被らなそうなショートカットを設定します。

すると、以下ように変更されます。

Ghosttyの設定

Ghosttyの設定は非常に簡単です。 とりあえず私は以下のように設定しています。

# パネル分割
keybind = super+s=new_split:down
keybind = shift+super+h=new_split:right

# パネル移動
keybind = super+h=goto_split:left
keybind = super+j=goto_split:bottom
keybind = super+k=goto_split:top
keybind = super+l=goto_split:right

Alfredのファイル検索でKarabiner-Elementsで設定しているVimキーバインド(Ctrl+j)で下に行こうとするとActionメニューが開いてしまう

例えばこんな検索をしてたとして、

こうなっちゃう。

他にも辞書とかアプリ起動でも同じ。

使い始めて何年も経ってからようやくどう直していいか分かった。
Preference > Features > Universal Actions の Show Actions から ctrl のチェックを外すだけだった。

JIRAの一括変更でスプリントを割り当てる

ここ1ヶ月くらいJIRAのBigGanttでWBSを引いてたんだけど、ようやくできあがったのでスプリントに割り振ろうと思った。
が、数が多すぎて挫けたので一括変更でやろうと思ったわけ。
JQLで

project = "myproj" and "Start date[Date]" >= '2023-11-06' and "Start date[Date]" <= '2023-11-10' ORDER BY cf[10004] DESC, due DESC, created DESC

みたいにすると例えば2023/11/6 〜 11/10の1週間にBigGanttで設定した開始日が入っているチケットが表示される。
あとは一括変更と思ったら、「スプリントID Sprint x は数字でなければなりません」だと。

IDなんて知らんわ!

調べてみたら、

注意:複数課題のスプリント フィールドを一括操作によって更新する場合、スプリント名ではなく、スプリント ID を入力する必要があります。スプリント ID を見つけるには、スプリント内の課題に移動して、スプリント名の上にカーソルを重ね、スプリントパラメーターの数値に対応する URL を調べます。

だそうです。

ja.confluence.atlassian.com

適当なチケットに設定したいスプリントを入れてURL見たら分かりました。

https://yourworkspace.atlassian.net/secure/GHGoToBoard.jspa?sprintId=742

あとはこのIDで一括変更するだけ。

絶対忘れる自信があるのでメモしておく。

go.mod に記述されているライブラリのバージョンを上げる

github.com/shirou/gopsutil 使って書いたライブラリがあるのですが、ちょっとしかテストを書いてなかったのでChatGPTにテストを書かせてみようと思い立ってごにょごにょやってみた。
が、久しぶりにテストを流したら何やらWarningが表示されます。

$ make test
go test ./... -v
# github.com/shirou/gopsutil/v3/disk
iostat_darwin.c:28:2: warning: 'IOMasterPort' is deprecated: first deprecated in macOS 12.0 [-Wdeprecated-declarations]
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/IOKit.framework/Headers/IOKitLib.h:143:1: note: 'IOMasterPort' has been explicitly marked deprecated here
# github.com/shirou/gopsutil/v3/host
smc_darwin.c:75:41: warning: 'kIOMasterPortDefault' is deprecated: first deprecated in macOS 12.0 [-Wdeprecated-declarations]
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/IOKit.framework/Headers/IOKitLib.h:133:19: note: 'kIOMasterPortDefault' has been explicitly marked deprecated here

ちょっと調べると以下のIssueが引っかかりました。

github.com

途中、 CGO_ENABLED="0" にしたら出なくなるよというのが書いてありますが、そうしたらメトリクス取れないじゃん…
最後まで読むと修正がMergeされてるようなのですが go mod tidy しても変わりはありません。

module github.com/gozuk16/gosi

go 1.16

require (
    github.com/inhies/go-bytesize v0.0.0-20201103132853-d0aed0d254f8
    github.com/shirou/gopsutil/v3 v3.21.4
)

今時点のgopsutilの最新はv3.23.5だからちょっと古いです。
そうか、go mod tidyしてもバージョンは上がらないのね。
調べたら以下のQ&Aが引っかかりました。

stackoverflow.com

go get で上げてから go mod tidy すればいいのね。Goのバージョンもだいぶ古いので先に上げとこう。

$ go version
go version go1.20.3 darwin/arm64

$ go mod tidy -go=1.20

$ go get -u
$ go mod tidy

結果、以下のようになりました。

module github.com/gozuk16/gosi

go 1.20

require (
    github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf
    github.com/shirou/gopsutil/v3 v3.23.5
)

require (
    github.com/go-ole/go-ole v1.2.6 // indirect
    github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
    github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
    github.com/shoenig/go-m1cpu v0.1.6 // indirect
    github.com/tklauser/go-sysconf v0.3.11 // indirect
    github.com/tklauser/numcpus v0.6.1 // indirect
    github.com/yusufpapurcu/wmi v1.2.3 // indirect
    golang.org/x/sys v0.9.0 // indirect
)

これでライブラリのバージョンが上がって、テストでWarning出なくなった!

ちなみにChatGPT(GPT-4)さんは普通にテストを書いてくれました。便利だなー。

Google Compute EngineのUbuntuサーバにLet’s Encryptの証明書を入れる

GCEの無料枠で1台動かしているサーバを作り直したのでLets encryptを入れる。
以前はcertbotというのを使ったが、今回はlegoというGo実装のCLIでやってみた。
しかしこれをやった後、ブログ記事として整理しようとあっためてる間にGoogle Domains終了のお知らせが出るとは。
さっさと投稿しろということか…

まずlegoググるACMEクライアントのHTTP-01 challengeを使うのがよく出てくるが、設定したいのはdevドメインなのでこのやり方は上手くいきません。httpsの設定をこれからやろうというのにhttpsじゃないとアクセス出来ないドメインなんだから。

そこでDNS-01 challengeを使ってみます。
DNSでTXTレコードに特定の文字列を入れて認証するというもののようです。

letsencrypt.org

私のdevドメインGoogle Domainsで管理しています。
legoGoogle Domainsはサポートされているようです。

go-acme.github.io

さっそくやってみます。
まずは GOOGLE_DOMAINS_ACCESS_TOKEN を作ります。
Google Domainsのセキュリティ開いたらACME DNS APIという項目があるの「トークンを作成」から作れます。簡単ですね!

apt でいれたlegoでやってみる。

GOOGLE_DOMAINS_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx== lego --email foo@example.com --dns googledomains --domains www.example.dev run

2023/06/04 10:26:10 unrecognized DNS provider: googledomains

あれあれ?

# apt info lego
Package: lego
Version: 4.1.3-3ubuntu1.22.04.1

なるほど。バージョンを満たしてないのか。
Configuration for Google Domains.

  • Code: googledomains
  • Since: v4.11.0

しかたないソースから入れるか。

go-acme.github.io

要件は以下の通り。

    go1.17+
    environment variable: GO111MODULE=on
$ sudo apt remove lego
$ sudo apt install golang-go

$ go version
go version go1.18.1 linux/amd64

$ go install github.com/go-acme/lego/v4/cmd/lego@latest
(snip)
# github.com/go-acme/lego/v4/providers/dns/arvancloud/internal
go/pkg/mod/github.com/go-acme/lego/v4@v4.12.0/providers/dns/arvancloud/internal/client.go:59:24: c.baseURL.JoinPath undefined (type *url.URL has no field or method JoinPath)
go/pkg/mod/github.com/go-acme/lego/v4@v4.12.0/providers/dns/arvancloud/internal/client.go:84:24: c.baseURL.JoinPath undefined (type *url.URL has no field or method JoinPath)
go/pkg/mod/github.com/go-acme/lego/v4@v4.12.0/providers/dns/arvancloud/internal/client.go:103:24: c.baseURL.JoinPath undefined (type *url.URL has no field or method JoinPath)
note: module requires Go 1.19
# github.com/go-acme/lego/v4/providers/dns/autodns/internal
go/pkg/mod/github.com/go-acme/lego/v4@v4.12.0/providers/dns/autodns/internal/client.go:63:24: c.BaseURL.JoinPath undefined (type *url.URL has no field or method JoinPath)
note: module requires Go 1.19
(snip)

最新バージョンだと新しいGoじゃないとダメっぽい。
Goも手動で入れるか。

$ cd /usr/local/src
$ sudo wget https://go.dev/dl/go1.20.4.linux-amd64.tar.gz
$ sudo tar xvfz go1.20.4.linux-amd64.tar.gz
$ sudo mv go ..  
$ sudo vi /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/go/bin"
(/usr/local/go/binを追加)

$ go version
go version go1.20.4 linux/amd64

もう一度インストール。

$ go install github.com/go-acme/lego/v4/cmd/lego@latest

何回やってもフリーズしてしまう。
2日くらいほっといたらCPU負荷は下がったけど上手くいってなかった…
仕方が無いので、ソースから入れよう。

$ sudo apt install git make
$ mkdir ~/go/src/github.com/go-acme
$ cd ~/go/src/github.com/go-acme
$ git clone https://github.com/go-acme/lego.git
$ cd lego
$ make

これもダメ。

もーあかん。Macでクロスコンパイルすることにするよ。
Goはこの辺が簡単でいいよね。

一応環境確認。

$ go env
GOARCH="amd64"
GOOS="linux"

こっからは自分のMacで作業。
ロスコンパイルのために確認した環境変数をセットしてビルドする。

$ git clone git@github.com:go-acme/lego.git
$ cd lego
$ export GOARCH="amd64"
$ export GOOS="linux"
$ make
BIN_OUTPUT: dist/lego
rm -rf dist/ builds/ cover.out
go generate ./...
go: downloading github.com/go-jose/go-jose/v3 v3.0.0
(snip)
fork/exec /var/folders/g_/xzlp1_r95r97tw7bxs1d5vs00000gn/T/go-build1172054503/b001/exe/dnsdocs: exec format error
internal/dnsdocs/generator.go:3: running "go": exit status 1
fork/exec /var/folders/g_/xzlp1_r95r97tw7bxs1d5vs00000gn/T/go-build1101727331/b001/exe/cli_help: exec format error
internal/dnsdocs/cli_help/generator.go:3: running "go": exit status 1
make: *** [generate-dns] Error 1

ありゃ?ドキュメント作ろうとしてエラーになってる?
Makefile見たら make build でビルドだけ動くということらしい。

$ make build
BIN_OUTPUT: dist/lego
rm -rf dist/ builds/ cover.out
Version: 60d2b55dd801c7f92c51b1624f348728b014c782
$ go build -trimpath -ldflags '-X "main.version=60d2b55dd801c7f92c51b1624f348728b014c782"' -o  dist/lego ./cmd/lego/

とりあえず出来た。
サーバに送りこんで使ってみよう。

$ scp -p  dist/lego foo@gce-host:/home/foo/.
$ ssh gce-host
$ sudo mv lego /usr/local/bin/.

今度は上手くいくかなー?
(※ここからはまたGCEのサーバで作業)

# GOOGLE_DOMAINS_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx== lego --email foo@example.com --dns googledomains --domains www.example.dev run

2023/06/08 14:22:38 No key found for account foo@example.com. Generating a P256 key.
2023/06/08 14:22:38 Saved key to /home/foo/.lego/accounts/acme-v02.api.letsencrypt.org/foo@example.com/keys/foo@example.com.key
2023/06/08 14:22:39 Please review the TOS at https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf
Do you accept the TOS? Y/n
Y
2023/06/08 14:22:42 [INFO] acme: Registering account for foo@example.com
!!!! HEADS UP !!!!

Your account credentials have been saved in your Let's Encrypt
configuration directory at "/home/foo/.lego/accounts".

You should make a secure backup of this folder now. This
configuration directory will also contain certificates and
private keys obtained from Let's Encrypt so making regular
backups of this folder is ideal.
2023/06/08 14:22:43 [INFO] [www.example.dev] acme: Obtaining bundled SAN certificate
2023/06/08 14:22:43 [INFO] [www.example.dev] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/235040568277
2023/06/08 14:22:43 [INFO] [www.example.dev] acme: Could not find solver for: tls-alpn-01
2023/06/08 14:22:43 [INFO] [www.example.dev] acme: Could not find solver for: http-01
2023/06/08 14:22:43 [INFO] [www.example.dev] acme: use dns-01 solver
2023/06/08 14:22:43 [INFO] [www.example.dev] acme: Preparing to solve DNS-01
2023/06/08 14:22:44 [INFO] [www.example.dev] acme: Trying to solve DNS-01
2023/06/08 14:22:44 [INFO] [www.example.dev] acme: Checking DNS record propagation using [127.0.0.53:53]
2023/06/08 14:22:46 [INFO] Wait for propagation [timeout: 2m0s, interval: 2s]
2023/06/08 14:22:53 [INFO] [www.example.dev] The server validated our request
2023/06/08 14:22:53 [INFO] [www.example.dev] acme: Cleaning DNS-01 challenge
2023/06/08 14:22:53 [INFO] [www.example.dev] acme: Validations succeeded; requesting certificates
2023/06/08 14:22:54 [INFO] [www.example.dev] Server responded with a certificate.

なんかそれっぽく出来てる。

nginxに証明書設定して、GCEのコンソールでファイヤーウォールを変更。443を許可。

$ cd /etc/nginx
$ sudo vi sites-available/hogehoge
        ssl on;
        ssl_certificate     /home/foo/.lego/certificates/www.example.dev.crt;
        ssl_certificate_key /home/foo/.lego/certificates/www.example.dev.key;
$ service nginx reload

みえたー!!!

SSH鍵認証でsudoする

GCEのブラウザコンソールからSSHすると、google-sudsersグループに追加されてsudo出来るんだけど、iTerm2から入るとグループに追加されないからsudo出来ない。
まあいいんだけど作業はiTerm2でやりたい。でもデフォルトユーザに弱いパスワードを設定してセキュリティは下げたくない。
というわけで、せっかくSSHしてるんだからSSHの鍵でsudo出来ないか調べたら以下のサイトがあったので参考にしながら設定してみた。

qiita.com

ますはリモートサーバでlibpam-ssh-agent-authを使うようにpamの設定をする。
参考:https://manpages.ubuntu.com/manpages/bionic/man8/pam_ssh_agent_auth.8.html

$ sudo apt search libpam-ssh-agent-auth
Sorting... Done
Full Text Search... Done
libpam-ssh-agent-auth/jammy 0.10.3-3.1ubuntu2 amd64
  PAM Authentication via forwarded ssh-agent

$ sudo apt install libpam-ssh-agent-auth

以下の行を先頭に追加。
authorized_keysはホームディレクトリの物を使う設定にした。
上手く動いたら debug は外してよい。

$ sudo vi /etc/pam.d/sudo

auth [success=3 default=ignore] pam_ssh_agent_auth.so file=~/.ssh/authorized_keys debug

sudoの設定追加もする。

$ sudo vi /etc/sudoers
Defaults        env_keep += "SSH_AUTH_SOCK" #←を追加

$ sudo vi /etc/sudoers.d/user
username ALL=(ALL) NOPASSWD:ALL # ← usernameを自分のログイン名にする

後はpamで設定したauthorized_keysに公開鍵を登録する訳なんだが、今使っているtailscale sshの場合、鍵管理は全部お任せで公開鍵がどこにあるか分からず、1passwordでSSH鍵管理するように変更することにした。
というわけで、tailscale sshをOFFにする。

$ sudo tailscale up --ssh=false

試しにsshしてみると1passwordの認証ダイアログが上がってきたので、切り替わっていることが分かる。

1passwordでSSHキーを生成。鍵タイプはEd25519にしておく。

生成した公開鍵をpamで設定したauthorized_keysに登録する。
今回初めてauthorized_keysを作成したのでパーミッションも変更しておく。

$ cat <<EOF >> ~/.ssh/authorized_keys
ssh-ed25519 生成した公開鍵
EOF

$ chmod 600 ~/.ssh/authorized_keys

1passwordのssh-agentが使われるように設定する。
ローカルの.ssh/configを見るとAgent転送の設定が無かったので追加。
sshしてみて環境変数 SSH_AUTH_SOCKがあればOK。

$ vi .ssh/config
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
ForwardAgent yes    # ← これを追加

$ ssh hogehoge
$ env | grep SSH_AUTH_SOCK
SSH_AUTH_SOCK=/tmp/ssh-XXXXXXXXX/agent.00000

ログインシェルの設定に以下を追加する。
自分の環境はzsh+zprestoで、自分用の設定ファイルを読むようにしているので以下のようにした。

$ vi ~/.zshrc-gozu

export SSH_AUTH_SOCK=~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock  # ←追加 

やったね!
これで、sudo出来ましたよと。

unisonでディレクトリを双方向同期する

この記事は、Infocom Advent Calendar 2022 4日目の記事です。

qiita.com

PCをオフライン環境で使いたいときには必要なファイルをコピーして持っていくことになり、作業中に何らかの更新が発生したら作業後はコピー元に戻しますね。
gitで管理すればいいものもあるしソースはそうしてますけど、パワポExcelとかまあいろんな雑多なファイルをgitに全部入れるわけにもいきません。
とはいえ毎回手動でコピーして戻すとなるとミスもするし、毎回毎回手作業でやるのはうんざりするので自動化を考えました。

  • シェルスクリプト/バッチファイルでコピーコマンドを使う
    • 全部コピーするので単純に遅い
    • 戻すのは手作業
  • rsync
    • 一方向しかできない
    • 両方に修正があると詰む
    • 結局、戻すのは手作業
  • unison
    • 両方の修正を検出してそれなりにいい感じにやってくれる。これだ!

unison

どうやら結構古くからあるUnix系のOSSツールのようです。
私は1995年頃からUnix系OSを使っているのですが知りませんでした。
LinuxMacだけじゃなくWindows用のバイナリもあったのはうれしい。
コンフリクトを検出できる。
SSHでも同期できる。
rsyncのような部分転送になっているということでコピーが高速。ファイル数が多ければそれなりに時間はかかりますが、普段使いの感じだと十分早いなと思います。
お手軽。これだいじ。

www.cis.upenn.edu

alliance.seas.upenn.edu

  • インストール

Macだとbrewのcaskがありました。

$ brew install unison
Warning: Treating unison as a formula. For the cask, use homebrew/cask/unison
🍺  /usr/local/Cellar/unison/2.53.0: 9 files, 3.9MB

Windowsはscoopで入れてます。

> scoop install unison
Installing 'unison' (2.53.0) [64bit] from main bucket
unison-v2.53.0+ocaml-4.12.1+x86_64.windows.zip (29.8 MB) [====================================================] 100%
Checking hash of unison-v2.53.0+ocaml-4.12.1+x86_64.windows.zip ... ok.
Extracting unison-v2.53.0+ocaml-4.12.1+x86_64.windows.zip ... done.
Linking ~\scoop\apps\unison\current => ~\scoop\apps\unison\2.53.0
Creating shim for 'unison'.
Creating shim for 'unison-fsmonitor'.
'unison' (2.53.0) was installed successfully!
Notes
-----
Compiled with same OCaml compiler version 4.12.1

main bucketにあったのでbucket追加しなくても普通に入ると思います。

使ってみる

ということで使ってみます。

  • コマンドで実行

unison root1 root2 で起動します。

$ tree foo bar
foo
├── aaa
│   └── hogehoge.pptx
└── hoge.txt
bar  [error opening dir]

$ unison foo bar
Unison 2.52.1 (ocaml 4.12.0): Contacting server...
Looking for changes
Warning: No archive files were found for these roots, whose canonical names are:
    /Users/gozu/Documents/foo
    /Users/gozu/Documents/bar

Reconciling changes

foo            bar
dir      ---->              [f]

Proceed with propagating updates? [] y
Propagating updates

[BGN] Copying  from /Users/gozu/Documents/foo to /Users/gozu/Documents/bar
[END] Copying

$ tree foo bar
foo
├── aaa
│   └── hogehoge.pptx
└── hoge.txt
bar
├── aaa
│   └── hogehoge.pptx
└── hoge.txt

初回なのでまるっとコピーされました。
相手のディレクトリが無ければ単純に複製ができます。

  • bar/hoge.txt を修正して、同期してみる
$ unison foo bar

foo            bar
         <---- new file   .hoge.txt.un~  [f]
         <---- changed    hoge.txt  [f]
         <---- new file   hoge.txt~  [f]

Proceed with propagating updates? [] y
Propagating updates

[BGN] Copying .hoge.txt.un~ from /Users/gozu/Documents/bar to /Users/gozu/Documents/foo
[END] Copying .hoge.txt.un~
[BGN] Updating file hoge.txt from /Users/gozu/Documents/bar to /Users/gozu/Documents/foo
[END] Updating file hoge.txt
[BGN] Copying hoge.txt~ from /Users/gozu/Documents/bar to /Users/gozu/Documents/foo
[END] Copying hoge.txt~

barのファイルがfooへ同期されています。
が、vimの管理ファイルまで同期されてますね。これは設定で除外できます。

  • 双方で同じファイルを更新してみる
$ unison foo bar

foo            bar
changed  <-?-> changed    hoge.txt  [] d

diff -u '/Users/gozu/Documents/bar/hoge.txt' '/Users/gozu/Documents/foo/hoge.txt'

--- /Users/gozu/Documents/bar/hoge.txt  2022-12-02 20:07:40.000000000 +0900
+++ /Users/gozu/Documents/foo/hoge.txt  2022-12-02 20:07:45.000000000 +0900
@@ -1,3 +1 @@
 hello unison.
-hello unison.
-hello unison.

changed  <-?-> changed    hoge.txt  [] x
foo          : changed file       modified on 2022-12-02 at 20:07:45  size 14        rw-r--r--
bar          : changed file       modified on 2022-12-02 at 20:07:40  size 42        rw-r--r--
changed  ====> changed    hoge.txt  [] >

Proceed with propagating updates? [] y
Propagating updates

[BGN] Updating file hoge.txt from /Users/gozu/Documents/foo to /Users/gozu/Documents/bar
[END] Updating file hoge.txt

修正したファイルがテキストファイルなのでdiffを取ることができました。
あとはファイルの日付やサイズを確認したり。
どちらを採用するか > で指定しています。
インタラクティブなコマンドは

atmarkit.itmedia.co.jp

このくらいでなんとかなるかな。

よく使う設定を定義できる

確かにいいんですが、これだとあんまり便利になってませんね。
いらないファイルを除外したり、新しいファイルを採用して欲しいし、いちいち指定せずとも全部自動でやって欲しい。
なので、設定ファイルを書いて自動実行できるようにします。
Macだと ~/.unison ディレクトリを作って、拡張子がprfのファイルを作ります。
Windowsでは C:\Users\アカウント名\.unison です)
default.prf は定義名を指定せずにunisonを実行したときに使われます。
unison 定義名 とすると ~/.unison/定義名.prf が使われます。

私の設定ファイルは以下のようになっています。

includeで読み込むための基本的な設定(common.prf)をしておくと、コピーする対象ごとに設定ファイルを作ることができ、最小の設定ファイルにできます。

$ cat ~/.unison/default.prf
# Unison preferences file
# https://88171.net/unison-manual-ja
# https://www.seas.upenn.edu/~bcpierce/unison/download/releases/stable/unison-manual.html

# 自動実行
batch = true

# ownerも同期する
owner = true

# タイムスタンプをコピーする(ディレクトリはできない)
times = true

# permissionを設定しない
perms = 0

# chmodしない perms=0と組み合わせる
dontchmod = true

# 新しいファイルを優先
prefer = newer

# 新規作成ファイルはユーザに同期如何を問わない
auto = true

# ファイル更新日時による更新有無の判定(Windowsでは更新日時が変わらないことがあるのでたまにfalseにするとよいらしい)
fastcheck = true

# エラー以外の出力停止をしない
silent = false

# 無視するディレクトリやファイル名を指定する
ignore = Name .DS_Store
ignore = Name Icon?
ignore = Name {.*.swp,*.*~}
ignore = Name {*.gsheet,*.gslides,*.gdoc}
$ cat ~/.unison/foo.prf
# Include the contents of the file common
include common

# 同期する対象のルートパスの定義
root = /Users/gozu/Documents/foo/
root = /Volumes/bar/foo/

# バックアップ
backup = Name *
maxbackups = 5
backupprefix = $VERSION.
backupdir = /Volumes/nas/backup/unison/foo

実行は、 unison foo と打つだけです。
こうして設定を分けておく事で、重要性に応じてバックアップの世代数や取るファイルの種類、ディレクトリを変えることができます。

設定のオプションやサンプルが色々載っているので、

https://www.seas.upenn.edu/~bcpierce/unison/download/releases/stable/unison-manual.html

を、見てやってます。DeepLを使うと驚くほどわかりやすく訳してくれます。

使い道

MacWindowsNASなどプラットフォームを越えて個人的なディレクトリを同期するのにはとても便利です。
ファイルを消すときはやっぱり注意が必要。どっちを消しても消える事になるので、rsyncの--deleteオプションより危険な事もあると思う。 更新する人が不特定多数なところで使うのちょっと躊躇しますね。
でも、ログやバックアップがいい感じに取れるので運用次第で使えるのかも?

双方向で同期出来て普通のコピーより早いし差分更新でマルチプラットフォームとなればもっと使われてもいいのになーと思ってこのブログを書きながら何となくググっていたらdocker-syncというツールで使われているというのが分かった。コンテナの中のファイルとホスト側のファイルを同期させるもののようです。
なるほど。いかにもなユースケースですね。