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というツールで使われているというのが分かった。コンテナの中のファイルとホスト側のファイルを同期させるもののようです。
なるほど。いかにもなユースケースですね。

VMWare FusionのWin10をWin11へ上げる

まだ無料アップグレードができるのでやってみる。
Windows 11のシステム要件として64GB以上のシステムDISKとTPMが引っかかったのでまずはこちらを解決しよう。
以下のサイトを参考に実施してみます。

softantenna.com

パーティション拡張する

仮想マシンだから簡単簡単、と思った意外と面倒だった。
VMWare側で拡張は簡単。
設定 > ハードディスク > ディスクサイズ を64GBにするだけ。

ところが起動したら、ブート領域/Cドライブ/回復パーティション/追加領域 になっていて拡張できない…

回復パーティションは設定画面からは消せないので以下のサイトを見ながらやってみる。

pc-karuma.net

C:\WINDOWS\system32>diskpart

DISKPART> list disk

  ディスク      状態           サイズ   空き   ダイナ GPT
  ###                                          ミック
  ------------  -------------  -------  -------  ---  ---
  ディスク 0    オンライン            64 GB    14 GB
  ディスク 1    オンライン            50 GB  1024 KB        *

DISKPART> select disk 0

ディスク 0 が選択されました。

DISKPART> list partition

  Partition ###  Type                Size     Offset
  -------------  ------------------  -------  -------
  Partition 1    プライマリ              579 MB  1024 KB
  Partition 2    プライマリ               48 GB   580 MB
  Partition 3    回復                 553 MB    49 GB

DISKPART> select partition 3

パーティション 3 が選択されました。

DISKPART> list partition

  Partition ###  Type                Size     Offset
  -------------  ------------------  -------  -------
  Partition 1    プライマリ              579 MB  1024 KB
  Partition 2    プライマリ               48 GB   580 MB
* Partition 3    回復                 553 MB    49 GB

DISKPART> delete partition override

DiskPart は選択されたパーティションを正常に削除しました。

DISKPART> list partition

  Partition ###  Type                Size     Offset
  -------------  ------------------  -------  -------
  Partition 1    プライマリ              579 MB  1024 KB
  Partition 2    プライマリ               48 GB   580 MB

DISKPART>

これで消えました。(Windows 11を入れたら復活させるかな)
システムパーティションを拡張できるようになったので全部まとめてしまう。

仮想マシンTPMを追加する

DISKは無事拡張できたので残りはTPMです。 手順としては、

  1. 起動ディスクをMBRからUEFIブートへ変更
  2. VMWareの設定でファームウエアをUEFIへ変更
  3. VMWareの設定でUEFIセキュアブートを有効化にチェック
  4. VMWareの設定で暗号化を有効にするをチェック
  5. Windows 11 インストール

という感じです。

起動ディスクをレガシーBIOSからUEFIへ変更する

VMWareの設定ではレガシーBIOSと出ていますがいわゆる昔ながらのMBRレコードから起動するように仮想マシンを作っていたのでこれをUEFIにするにはディスクをGPTへ変更しなければなりません。
この辺の操作は危険なのでスナップショットを取ってから作業を行います。
仮想マシンを起動したら管理者モードでコマンドプロンプトを起動して、mbr2gptコマンドで実施します。

まず確認ですが早速エラー。

C:\WINDOWS\system32>mbr2gpt /validate /disk:0
ERROR: MBR2GPT can only be used from the Windows Preinstallation Environment. Use /allowFullOS to override.

C:\WINDOWS\system32>mbr2gpt /validate /disk:0 /allowFullOS
MBR2GPT: Attempting to validate disk 0
MBR2GPT: Retrieving layout of disk
MBR2GPT: Validating layout, disk sector size is: 512 bytes
Cannot find OS partition(s) for disk 0

answers.microsoft.com

www.diskpart.com

ググってみるとどうやらWindowsのバージョンアップを行った際に残ったBCD構成ファイル内のゴミがいけないとのこと。消してみましょう。

まず不可視のブートパーティションをいじれるようにします。

C:\WINDOWS\system32>diskpart

DISKPART> select disk 0

ディスク 0 が選択されました。

DISKPART> list partition

  Partition ###  Type                Size     Offset
  -------------  ------------------  -------  -------
  Partition 1    プライマリ              579 MB  1024 KB
  Partition 2    プライマリ               63 GB   580 MB

DISKPART> select partition 2

パーティション 2 が選択されました。

DISKPART> assign letter=w:

仮想ディスク サービス エラー:
現在のブート ボリュームまたはページ ファイル ボリュームに対して、
ドライブ文字を割り当てたり、割り当てを解除することはできません。


DISKPART> select partition 1

パーティション 1 が選択されました。

DISKPART> assign letter=b:

DiskPart はドライブ文字またはマウント ポイントを正常に割り当てました。

DISKPART> exit

DiskPart を終了しています...

元記事ではシステムパーティションをWドライブに退避して作業を行なっていましたが、Cドライブから変えられませんでした。
ブートパーティションはBドライブに変えられたのでこのまま作業を続行します。

C:\WINDOWS\system32>dir /a b:
 ドライブ B のボリューム ラベルは システムで予約済み です
 ボリューム シリアル番号は 3CA5-6110 です

 B:\ のディレクトリ

2022/03/21  22:26    <DIR>          Boot
2022/03/21  21:42           413,880 bootmgr
2019/12/07  18:08                 1 BOOTNXT
2021/06/02  14:50             8,192 BOOTSECT.BAK
2020/12/25  11:02    <DIR>          System Volume Information
               3 個のファイル             422,073 バイト
               2 個のディレクトリ     572,878,848 バイトの空き領域

ブートパーティション見えてますね。

bcdeditコマンドでunknownになっているidentifierを探します。
今回は {1cea4e0a-4655-11eb-b8e8-86cd67d75de6} ですね。

C:\WINDOWS\system32>bcdedit /store B:\boot\bcd /enum all

Windows ブート マネージャー
--------------------------------
identifier              {bootmgr}
device                  partition=B:
description             Windows Boot Manager
locale                  ja-JP
inherit                 {globalsettings}
default                 {default}
resumeobject            {1cea4e07-4655-11eb-b8e8-86cd67d75de6}
displayorder            {default}
toolsdisplayorder       {memdiag}
timeout                 30

Windows ブート ローダー
--------------------------------
identifier              {default}
device                  partition=C:
path                    \WINDOWS\system32\winload.exe
description             Windows 10
locale                  ja-JP
inherit                 {bootloadersettings}
recoverysequence        {1cea4e0a-4655-11eb-b8e8-86cd67d75de6}
displaymessageoverride  Recovery
recoveryenabled         Yes
allowedinmemorysettings 0x15000075
osdevice                partition=C:
systemroot              \WINDOWS
resumeobject            {1cea4e07-4655-11eb-b8e8-86cd67d75de6}
nx                      OptIn
bootmenupolicy          Standard

Windows ブート ローダー
--------------------------------
identifier              {1cea4e0a-4655-11eb-b8e8-86cd67d75de6}
device                  ramdisk=[unknown]\Recovery\WindowsRE\Winre.wim,{1cea4e0b-4655-11eb-b8e8-86cd67d75de6}
path                    \windows\system32\winload.exe
description             Windows Recovery Environment
locale                  ja-JP
inherit                 {bootloadersettings}
displaymessage          Recovery
osdevice                ramdisk=[unknown]\Recovery\WindowsRE\Winre.wim,{1cea4e0b-4655-11eb-b8e8-86cd67d75de6}
systemroot              \windows
nx                      OptIn
bootmenupolicy          Standard
winpe                   Yes

休止状態からの再開
--------------------------------
identifier              {1cea4e07-4655-11eb-b8e8-86cd67d75de6}
device                  partition=C:
path                    \WINDOWS\system32\winresume.exe
description             Windows Resume Application
locale                  ja-JP
inherit                 {resumeloadersettings}
recoverysequence        {1cea4e0a-4655-11eb-b8e8-86cd67d75de6}
recoveryenabled         Yes
allowedinmemorysettings 0x15000075
filedevice              partition=C:
filepath                \hiberfil.sys
bootmenupolicy          Standard
debugoptionenabled      No

Windows メモリ テスター
--------------------------------
identifier              {memdiag}
device                  partition=B:
path                    \boot\memtest.exe
description             Windows メモリ診断ツール
locale                  ja-JP
inherit                 {globalsettings}
badmemoryaccess         Yes

EMS 設定
--------------------------------
identifier              {emssettings}
bootems                 No

デバッガー設定
--------------------------------
identifier              {dbgsettings}
debugtype               Local

RAM 不良
--------------------------------
identifier              {badmemory}

グローバル設定
--------------------------------
identifier              {globalsettings}
inherit                 {dbgsettings}
                        {emssettings}
                        {badmemory}

ブート ローダー設定
--------------------------------
identifier              {bootloadersettings}
inherit                 {globalsettings}
                        {hypervisorsettings}

ハイパーバイザー設定
-------------------
identifier              {hypervisorsettings}
hypervisordebugtype     Serial
hypervisordebugport     1
hypervisorbaudrate      115200

再開ローダー設定
--------------------------------
identifier              {resumeloadersettings}
inherit                 {globalsettings}

こいつを消します。

C:\WINDOWS\system32>bcdedit /store B:\boot\bcd /delete {1cea4e0a-4655-11eb-b8e8-86cd67d75de6}
この操作を正しく終了しました。


C:\WINDOWS\system32>bcdedit /store B:\boot\bcd /enum all

Windows ブート マネージャー
--------------------------------
identifier              {bootmgr}
device                  partition=B:
description             Windows Boot Manager
locale                  ja-JP
inherit                 {globalsettings}
default                 {default}
resumeobject            {1cea4e07-4655-11eb-b8e8-86cd67d75de6}
displayorder            {default}
toolsdisplayorder       {memdiag}
timeout                 30

Windows ブート ローダー
--------------------------------
identifier              {default}
device                  partition=C:
path                    \WINDOWS\system32\winload.exe
description             Windows 10
locale                  ja-JP
inherit                 {bootloadersettings}
displaymessageoverride  Recovery
recoveryenabled         Yes
allowedinmemorysettings 0x15000075
osdevice                partition=C:
systemroot              \WINDOWS
resumeobject            {1cea4e07-4655-11eb-b8e8-86cd67d75de6}
nx                      OptIn
bootmenupolicy          Standard

休止状態からの再開
--------------------------------
identifier              {1cea4e07-4655-11eb-b8e8-86cd67d75de6}
device                  partition=C:
path                    \WINDOWS\system32\winresume.exe
description             Windows Resume Application
locale                  ja-JP
inherit                 {resumeloadersettings}
recoveryenabled         Yes
allowedinmemorysettings 0x15000075
filedevice              partition=C:
filepath                \hiberfil.sys
bootmenupolicy          Standard
debugoptionenabled      No

Windows メモリ テスター
--------------------------------
identifier              {memdiag}
device                  partition=B:
path                    \boot\memtest.exe
description             Windows メモリ診断ツール
locale                  ja-JP
inherit                 {globalsettings}
badmemoryaccess         Yes

EMS 設定
--------------------------------
identifier              {emssettings}
bootems                 No

デバッガー設定
--------------------------------
identifier              {dbgsettings}
debugtype               Local

RAM 不良
--------------------------------
identifier              {badmemory}

グローバル設定
--------------------------------
identifier              {globalsettings}
inherit                 {dbgsettings}
                        {emssettings}
                        {badmemory}

ブート ローダー設定
--------------------------------
identifier              {bootloadersettings}
inherit                 {globalsettings}
                        {hypervisorsettings}

ハイパーバイザー設定
-------------------
identifier              {hypervisorsettings}
hypervisordebugtype     Serial
hypervisordebugport     1
hypervisorbaudrate      115200

再開ローダー設定
--------------------------------
identifier              {resumeloadersettings}
inherit                 {globalsettings}

もうmbr2gptを一回実行してみます。

C:\WINDOWS\system32>mbr2gpt /validate /disk:0 /allowFullOS
MBR2GPT: Attempting to validate disk 0
MBR2GPT: Retrieving layout of disk
MBR2GPT: Validating layout, disk sector size is: 512 bytes
MBR2GPT: Validation completed successfully

うまくいったようです。

ではGPTへ変換してみましょう。

C:\WINDOWS\system32>mbr2gpt /convert /disk:0 /allowFullOS

MBR2GPT will now attempt to convert disk 0.
If conversion is successful the disk can only be booted in GPT mode.
These changes cannot be undone!

MBR2GPT: Attempting to convert disk 0
MBR2GPT: Retrieving layout of disk
MBR2GPT: Validating layout, disk sector size is: 512 bytes
MBR2GPT: Trying to shrink the OS partition
MBR2GPT: Creating the EFI system partition
MBR2GPT: Installing the new boot files
MBR2GPT: Performing the layout conversion
MBR2GPT: Migrating default boot entry
MBR2GPT: Fixing drive letter mapping
MBR2GPT: Conversion completed successfully
Call WinReReapir to repair WinRE
MBR2GPT: Failed to update ReAgent.xml, please try to  manually disable and enable WinRE.
MBR2GPT: Before the new system can boot properly you need to switch the firmware to boot to UEFI mode!

回復パーティションを消したから怒られてるみたいだけど、一応GPTにはできたっぽいですね。
シャットダウンしてVMWareの設定を変えます。

VMWareの設定を変更する

VMWare Fusionの設定をレガシーBIOSUEFI

セキュアブートにする前に起動して確認します。

うまくいったのでシャットダウンしてからセキュアブートにします。

お次は、暗号化を有効にする にチェックです。
これをしないとTPMを追加できません。
DISK容量が足りないと暗号化できません。

最後にTPMモジュール追加です。

バイスを追加するだけですが、元には戻せないと言われます。

Windows 11のインストール

正常性のチェックは通るのに、WindowsUpdateの画面を開くとシステム要件を満たしてないと言われたので、WindowsUpdateしてみましたが変わらず。

でも、Windows11 インストール アシスタントを実行してみたところインストールできました!

追記

Windows 11にしたら自動的に回復パーティションが作成されていました。
作り直す必要なかったですね。
さらにEFIシステムパーティションというものも追加されています。
BドライブにしていたのはGUIからドライブレターの削除ができました。

1Password 8に上げてSSHキーを管理出来るようにする

1Password 8で見た目が変わったり機能が増えたりしていますが、自分が一番おっと思ったのはSSHキーを管理出来るという所ですね。
というわけで、メモがてら記事にしていこうと思います。

インストール

いま(2022/7/9)時点でまだMac AppStoreには8はきていないのでダウンロードします。

1password.com

1Password Installer.app を起動すれば自動的にアップデートされます。

だいぶ見た目が変わってます。(ダークモードに対応したのね)

設定

まずは設定を有効にします。
1Password > 環境設定 > 開発者
を開きます。

デフォルトだと何もチェックがついてない状態ですね。

SSHエージェントを使用する にチェックします。
1Password CLI用の生体認証ロック解除op というコマンドを使うようなのだがとりあえず今は使わないのでそのままです。

1PasswordがSSHキー名をディスクに保存出来るようにします を許可します。
FingerPrintを表示されても分からんしね。

~/.ssh/config に表示されているスニペットを追加します。(xxxxxxxxxxのところのパスは環境によって違うようです)

Host *
    IdentityAgent "~/Library/Group Containers/xxxxxxxxxx.com.1password/t/agent.sock"

Remember key approval が、until 1Password locks でも until 1Password quits でもどちらにしろロックしたらTouch IDが出てくるので、自動ロックの間隔を調整してもいいんじゃないでしょうか。
デフォルトの10分は自分には厳しすぎます。
1Password > 環境設定 > セキュリティ > 自動ロック
で、変更しちゃいます。

既存のキーを使ってSSHする

ラズパイへのSSHキーを1Passwordで管理してみます。
キーを作るときにEd25519かRSA(2048ビット以上)にしていない場合は作り直す必要があります。
お勧めはEd25519らしいです。

とりあえず既にあるRSAの鍵をインポートします。
+新規アイテム > SSHキー で登録します。

秘密鍵を追加 からキーファイルを読み込む でもいいし、ラズパイからわざわざ持ってこなこなくても クリップボードから鍵を張り付け で登録出来ます。
既存のキーならローカルにあるかもしれないですが、クリップボード貼り付けができるのは地味に便利。
パスフレーズが設定されていれば入力画面が出てきます。

キーが登録出来ました。

それではログインしてみます。
既存のキーなので ~/.ssh/authorized_keys は設定済みの想定です。

Touch IDでログイン出来ました!

ssh-keygenを使わずに1Passwordで新しいキーを作る事も出来ます。
その場合、秘密鍵をどこにもコピーせずに公開鍵をauthorized_keysに登録するだけなのでちょっと安心ですね。

Git Cloneできるようにする

個人ではGitHub、仕事ではBitbucketを使っているので両方試してみます。

GitHub

まずGitHubから。
Settings > SSH and GPG keys へ行きます。

New SSH keyを押します。

Titleをクリックすると、1Passwordのブラウザ拡張が入っていれば SSHキーの作成 が出ます。

作成と入力を押すとキーが作成されて1Passwordへ登録され、GitHubの画面に反映されますので、Add SSH key を押して登録します。

それではCloneしてみます。
まずはCLIで。

git clone git@github.com:gozuk16/test.git
Cloning into 'test'...
remote: Enumerating objects: 35, done.
remote: Counting objects: 100% (14/14), done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 35 (delta 5), reused 14 (delta 5), pack-reused 21
Receiving objects: 100% (35/35), 5.04 KiB | 1.26 MiB/s, done.
Resolving deltas: 100% (9/9), done.

出来ました。

次に自分が使ってるGitクライアントのForkでやってみます。
CloneするときのプロトコルhttpsからSSHに切り替えます。

1PasswordがTouch IDの画面を出してくれるのでピッと触ればCloneできます。

うーん便利!

ちなみに、GitHubは2023年末までにTwo-factor authenticationを設定せよというわけで2FAも1Passwordで設定しています。
Sign inの時に1Passwordでパスワードを入力すると遷移先ではtokenが自動でセットされるので便利です。(まあいいという事にしといて...)

Bitbucket

基本的には同じです。
Personal settings > SSH 鍵 > 鍵を追加 でGitHubと同じように出来ます。

その他

  • いまのところ保管庫はデフォルトで作られるPersonalじゃないとダメみたいです

増えてくるとちょっと分かりにくくなるので、別の保管庫も何れ使えるようになるといいな。

  • TailscaleにSSHの接続管理をしてくれる機能が追加されたようです

tailscale.com

codezine.jp

TailscaleのエージェントにSSHを内包してるのかな?
sshdを立てる必要すらないという事で、結構すごいなーと思った。
こうなると1Passwordとか言ってる次元じゃないよね。
調べようと思ってるけど、いまのところ会社でTailscale使えないので何となく先送りしてる。。。