Macのログインシェルをzsh+Preztoに変更した

どうでもいい話ですが、昔はtcshを常用していましたが今はzshを使ってます。
でも最近はデフォルトのbashのままのサーバもあったりして自分も丸くなったなーと思うけど、新しく買ったMacbook Air使ってて流石に不便なので頑張りました。

zshに変更

Homebrewでzshをインストールしてデフォルトのログインシェルを変更

$ brew install zsh
$ sudo vi /etc/chsh
/usr/local/bin/zsh ←追加

$ chsh -s /usr/local/bin/zsh

Prezto導入

インストール

git clone --recursive https://github.com/sorin-ionescu/prezto.git "${ZDOTDIR:-$HOME}/.zprezto"
$ setopt EXTENDED_GLOB
for rcfile in "${ZDOTDIR:-$HOME}"/.zprezto/runcoms/^README.md(.N); do
  ln -s "$rcfile" "${ZDOTDIR:-$HOME}/.${rcfile:t}"
done
ln: /Users/gozu/.zshrc: File exists

.zshrcがすでに存在するとだめみたい

$ mv .zshrc .zshrc.org

$ setopt EXTENDED_GLOB
for rcfile in "${ZDOTDIR:-$HOME}"/.zprezto/runcoms/^README.md(.N); do
  ln -s "$rcfile" "${ZDOTDIR:-$HOME}/.${rcfile:t}"
done
ln: /Users/gozu/.zlogin: File exists
ln: /Users/gozu/.zlogout: File exists
ln: /Users/gozu/.zpreztorc: File exists
ln: /Users/gozu/.zprofile: File exists
ln: /Users/gozu/.zshenv: File exists

lrwxr-xr-x   1 gozu  staff      35 11  4 17:24 .zlogin -> /Users/gozu/.zprezto/runcoms/zlogin
lrwxr-xr-x   1 gozu  staff      36 11  4 17:24 .zlogout -> /Users/gozu/.zprezto/runcoms/zlogout
lrwxr-xr-x   1 gozu  staff      38 11  4 17:24 .zpreztorc -> /Users/gozu/.zprezto/runcoms/zpreztorc
lrwxr-xr-x   1 gozu  staff      37 11  4 17:24 .zprofile -> /Users/gozu/.zprezto/runcoms/zprofile
lrwxr-xr-x   1 gozu  staff      35 11  4 17:24 .zshenv -> /Users/gozu/.zprezto/runcoms/zshenv
lrwxr-xr-x   1 gozu  staff      34 11  4 17:26 .zshrc -> /Users/gozu/.zprezto/runcoms/zshrc

中途半端に処理が行われていたようなので消してもう一度

$ rm .zlogin.org
$ rm .zlogout.org
$ rm .zpreztorc
$ rm .zprofile
$ rm .zshenv
$ rm .zshrc

$ setopt EXTENDED_GLOB
for rcfile in "${ZDOTDIR:-$HOME}"/.zprezto/runcoms/^README.md(.N); do
  ln -s "$rcfile" "${ZDOTDIR:-$HOME}/.${rcfile:t}"
done

OK!
新しいセッションを開けばPrestoで起動します。

設定

デフォルトのテーマが見ずらいのでテーマを変えます。

  • 一覧
$ prompt -l
Currently available prompt themes:
agnoster cloud damoekri giddie kylewest minimal nicoulaj paradox peepcode powerlevel10k powerlevel9k powerline pure skwp smiley sorin steeef adam1 adam2 bart bigfade clint default elite2 elite fade fire off oliver pws redhat restore suse walters zefram
  • プレビュー
$ prompt -p
(全部出るけど所々文字化け)

PowerLine対応フォントが入ってないのでRictyの対応版をインストールします。
ここはいつも使っている本家のRicty入れておきます。(Diminishedの方が楽だけど使い慣れてるので)

www.konosumi.net

$ brew tap sanemat/font
$ brew install ricty
(snip)
==> ricty
***************************************************
Generated files:
  /usr/local/opt/ricty/share/fonts/RictyDiscord-Regular.ttf
      /usr/local/opt/ricty/share/fonts/Ricty-Oblique.ttf
      /usr/local/opt/ricty/share/fonts/Ricty-Bold.ttf
      /usr/local/opt/ricty/share/fonts/RictyDiscord-Oblique.ttf
      /usr/local/opt/ricty/share/fonts/Ricty-Regular.ttf
      /usr/local/opt/ricty/share/fonts/Ricty-BoldOblique.ttf
      /usr/local/opt/ricty/share/fonts/RictyDiscord-Bold.ttf
      /usr/local/opt/ricty/share/fonts/RictyDiscord-BoldOblique.ttf
***************************************************
To install Ricty:
  $ cp -f /usr/local/opt/ricty/share/fonts/Ricty*.ttf ~/Library/Fonts/
  $ fc-cache -vf
***************************************************

$ brew options ricty
--with-patch-in-place
    Patch Powerline glyphs directly into Ricty fonts without creating new 'for Powerline' fonts
--with-powerline
    Patch for Powerline
--without-fullwidth
    Disable fullwidth ambiguous characters
--without-visible-space
    Disable visible zenkaku space
(使えるオプションを表示)

$ brew reinstall --with-powerline --with-patch-in-place ricty
==> Reinstalling sanemat/font/ricty --with-powerline --with-patch-in-place
==> Downloading http://yusa.lab.uec.ac.jp/~yusa/ricty/ricty_generator-4.1.1.sh
Already downloaded: /Users/gozu/Library/Caches/Homebrew/downloads/aacf520745eb723ecce41723f843ab10ead03a7323099ce2b7c51123d0c01e77--ricty_generator-4.1.1.sh
==> Downloading https://osdn.jp/frs/redir.php?m=gigenet&f=%2Fmix-mplus-ipa%2F63545%2Fmigu-1m-20150712.zip
Already downloaded: /Users/gozu/Library/Caches/Homebrew/downloads/4e24ddf8bf40fc096f34c12ec91e4469efcded452334dcbefb265ca0343ba455--migu-1m-20150712.zip
==> Downloading https://github.com/google/fonts/raw/f0e90b27b6e567af9378952a37bc8cf29e2d88e9/ofl/inconsolata/Inconsolata-
Already downloaded: /Users/gozu/Library/Caches/Homebrew/downloads/24a8808bea96ca8579bc893547b86241448b2944cca2414aca6d6400986529a3--Inconsolata-Regular.ttf
==> Downloading https://github.com/google/fonts/raw/f0e90b27b6e567af9378952a37bc8cf29e2d88e9/ofl/inconsolata/Inconsolata-
Already downloaded: /Users/gozu/Library/Caches/Homebrew/downloads/0f8e8be9545eb996c487dc08e9d95b408e9b5d0084889938d588d7bf7c98c31f--Inconsolata-Bold.ttf
==> Downloading https://github.com/powerline/fontpatcher/archive/c3488091611757cb02014ed7ed2f11be0208da83.zip
==> Downloading from https://codeload.github.com/powerline/fontpatcher/zip/c3488091611757cb02014ed7ed2f11be0208da83
######################################################################## 100.0%
==> Patching
patching file scripts/powerline-fontpatcher
==> sh ./ricty_generator-4.1.1.sh Inconsolata-Regular.ttf Inconsolata-Bold.ttf migu-1m-regular.ttf migu-1m-bold.ttf
==> fontforge -lang=py -script /private/tmp/ricty-20191104-6196-hotccb/scripts/powerline-fontpatcher --no-rename RictyDis
==> fontforge -lang=py -script /private/tmp/ricty-20191104-6196-hotccb/scripts/powerline-fontpatcher --no-rename Ricty-Ob
==> fontforge -lang=py -script /private/tmp/ricty-20191104-6196-hotccb/scripts/powerline-fontpatcher --no-rename Ricty-Bo
==> fontforge -lang=py -script /private/tmp/ricty-20191104-6196-hotccb/scripts/powerline-fontpatcher --no-rename RictyDis
==> fontforge -lang=py -script /private/tmp/ricty-20191104-6196-hotccb/scripts/powerline-fontpatcher --no-rename Ricty-Re
==> fontforge -lang=py -script /private/tmp/ricty-20191104-6196-hotccb/scripts/powerline-fontpatcher --no-rename Ricty-Bo
==> fontforge -lang=py -script /private/tmp/ricty-20191104-6196-hotccb/scripts/powerline-fontpatcher --no-rename RictyDis
==> fontforge -lang=py -script /private/tmp/ricty-20191104-6196-hotccb/scripts/powerline-fontpatcher --no-rename RictyDis
==> Caveats
***************************************************
Generated files:
  /usr/local/opt/ricty/share/fonts/RictyDiscord-Regular.ttf
      /usr/local/opt/ricty/share/fonts/Ricty-Oblique.ttf
      /usr/local/opt/ricty/share/fonts/Ricty-Bold.ttf
      /usr/local/opt/ricty/share/fonts/RictyDiscord-Oblique.ttf
      /usr/local/opt/ricty/share/fonts/Ricty-Regular.ttf
      /usr/local/opt/ricty/share/fonts/Ricty-BoldOblique.ttf
      /usr/local/opt/ricty/share/fonts/RictyDiscord-Bold.ttf
      /usr/local/opt/ricty/share/fonts/RictyDiscord-BoldOblique.ttf
***************************************************
To install Ricty:
  $ cp -f /usr/local/opt/ricty/share/fonts/Ricty*.ttf ~/Library/Fonts/
  $ fc-cache -vf
***************************************************
==> Summary
🍺  /usr/local/Cellar/ricty/4.1.1: 12 files, 27MB, built in 3 minutes 15 seconds
(PowerLine対応パッチを当てて再インストール。"--with-patch-in-place ricty"は新しいフォントを作らず元のファイルにパッチを当てるということらしい)

$ cp -f /usr/local/opt/ricty/share/fonts/Ricty*.ttf ~/Library/Fonts/
$ fc-cache -vf

iTerm2のフォントにRictyを指定

$ prompt -p
(ちゃんと出たー(≧∇≦)b)

※ 後で気がついたけど、今どきのiTerm2は、 Preferences > Profiles > Text > []Use built-in Powerline glyphs でPowerLine表示できるのね。ゴニョゴニョする必要なかったのか・・・。

  • iTerm2との調整

Preztoのテーマはagnosterで良さそうなのだが、iTerm2の透過を有効にしていると字が暗くて見ずらい。
なのでiTerm2のテーマを変えてみる。
Preferences > Profiles > Colors > Color Presets... から「Pastel(Dark Background)」にする。結構良くなったけど透過率が高めなら見やすいかなという感じ。
透過率低めなら、Visit Online GalleryからHybridを選ぶと良さそう。
※ 透過設定(Preferences > Profiles > Window > Transeparency)をいじったら下にある Keep background colors opaqueにチェックを入れる。PowerLineの三角が綺麗に塗られるようになります。

iterm2colorschemes.com

とりあえず、iTerm2の透過率(Transparency)は10%程度にしてテーマをHybrid、Preztoのテーマをagnosterでやってみることにした。

$ prompt agnoster
(agnosterを試してみる)

$ vi ~/.zpreztorc
#zstyle ':prezto:module:prompt' theme 'sorin'
zstyle ':prezto:module:prompt' theme 'agnoster'

$ source ~/.zpreztorc

ということでこんな感じになりました。

f:id:gozuk16:20191104193012p:plain

AndroidのUSBテザリングでMacを繋ぐ

この間のCROSS Party 2019に行ったときにWi-Fiと電源が無かったので、急遽USBテザリングでしのいだメモ。
きっかけは、Wi-FiテザリングだとAndroidの電池が心もとないのでどうしようと思って、ちょっと充電できないかとMacbook AirAndroid(Xperia XZ2 Compact)をUSB-Cケーブルで繋いでみたことです。
なんか通知を見てたらUSBテザリングできそうだったのでこの方が消費電力が抑えられるかなと思ってやってみました。

officeforest.org

を参考にしました。

Android : Xperia XZ2 Compact (SO-05K)

Macと繋ぐと通知が来るのでONにできますが、設定は以下の通りにしました。
設定 > ネットワークとインターネット > テザリング > USBテザリング > ON

Mac : Macbook Air 2019

ドライバが必要なので以下のサイトからダウンロードしてインストールします。

joshuawise.com

HoRNDIS-9.2.pkg をダウンロードしてインストールしました。
この時ばかりはWi-Fiテザリング使いました。

インストールして再起動すると システム環境設定 > ネットワーク に SO-05K が追加されていました。

困ったこと

いとも簡単にできたのですが問題が一つ・・・

設定 > 機器接続 > USBの設定 で、 接続機器の充電ON にするとAndroidからMacへ充電が行われるのですが、 OFF にすると今度はMacからAndroidの充電が行われます!

両側USB-Cだからなんだろうか?
充電を止めることはできないのか?

Nature Remo miniの室温データをinfluxdbに入れてみる

Nature Remo mini 使って寝ている間に暑すぎればクーラーON、寒すぎればOFFしています。
買ってすぐの頃は温度設定がイマイチでうまく行ってなくて暑いのにONされなかったり、夜中に寒すぎて手動でOFFしたりしてました。

そこで室温データを分析してうまくやりたいと思ってNature Remo Cloud APIを叩いて室温を取得してからGoogle Spreadsheetに書き込むというのを書いたことがありました。
ただ設定を試行錯誤しているうちにいい感じになってきたのでデータを使うことはありませんでした・・・

最近InfluxDBを触っていたのでSpreadsheetの代わりにDBへ入れたほうがいいかなと思って修正してみたらむしろ簡単になったのでこちらを記録に残しておくことにします。

前提としてMacで作業していてGoでプログラム書いてます。
(常時動かすときはLinuxで動かすつもりですけど)

室温データの取得

Nature Remo Cloud API

developer.nature.global

アクセストークンが必要です。トーク無しでAPIを叩くとUnauthorizedになります。
トークンを公開しないように気をつけましょう。

バイスの情報を取りたいので /1/devices というAPIを使ってみます。

swagger.nature.global

Goで書いてるのでまずはJSONが欲しいということでトークンを使って取得してみます。(${Token}を置き換える)

$ curl -X GET "https://api.nature.global/1/devices" -H "accept: application/json" -k --header "Authorization: Bearer ${Token}"
[{"name":"3F_Remo","id":"xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","created_at":"2018-08-06T13:12:11Z","updated_at":"2019-09-30T14:12:13Z","mac_address":"xx:xx:xx:xx:xx:xx","serial_number":"xxxxxxxxx","firmware_version":"Remo-mini/1.0.87-g8b06f0e","temperature_offset":-2,"humidity_offset":0,"users":[{"id":"xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx","nickname":"GOZU Kenichiro","superuser":true}],"newest_events":{"te":{"val":27.2,"created_at":"2019-10-01T00:13:52Z"}}}]

こんな感じで取れました。
見ずらいので整形したものを貼っておきます。

[
    {
        "name": "3F_Remo",
        "id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "created_at": "2018-08-06T13:12:11Z",
        "updated_at": "2019-09-30T14:12:13Z",
        "mac_address": "xx:xx:xx:xx:xx:xx",
        "serial_number": "xxxxxxxxx",
        "firmware_version": "Remo-mini/1.0.87-g8b06f0e",
        "temperature_offset": -2,
        "humidity_offset": 0,
        "users": [
            {
                "id": "xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx",
                "nickname": "GOZU Kenichiro",
                "superuser": true
            }
        ],
        "newest_events": {
            "te": {
                "val": 27.2,
                "created_at": "2019-10-01T00:13:52Z"
            }
        }
    }
]

newest_events.te.val が室温ですね。27.2度でした。

GoでJSON扱うために struct を書くのが鬱陶しいのでjson2goサイトでサクッと変換します。

mholt.github.io

type RemoDevices []struct {
    Name              string    `json:"name"`
    ID                string    `json:"id"`
    CreatedAt         time.Time `json:"created_at"`
    UpdatedAt         time.Time `json:"updated_at"`
    MacAddress        string    `json:"mac_address"`
    SerialNumber      string    `json:"serial_number"`
    FirmwareVersion   string    `json:"firmware_version"`
    TemperatureOffset int       `json:"temperature_offset"`
    HumidityOffset    int       `json:"humidity_offset"`
    Users             []struct {
        ID        string `json:"id"`
        Nickname  string `json:"nickname"`
        Superuser bool   `json:"superuser"`
    } `json:"users"`
    NewestEvents struct {
        Te struct {
            Val       float64   `json:"val"`
            CreatedAt time.Time `json:"created_at"`
        } `json:"te"`
    } `json:"newest_events"`
}

配列になってます。うちにはRemoは1つしかないから常に1しか返ってこないけどね。。。

あとは API 叩いてレスポンスのJSONからteを取得します。
まずは net/httpを使ってhttp clientの準備。

import (
    "fmt"
    "log"
    "net/http"
    "net/http/httputil"
)

func newRequest(m map[string]string) (*http.Request, error) {
    url := m["url"]

    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return nil, err
    }

    req.Header.Set("Content-Type", "application/json")
    req.Header.Set(m["headerKey"], m["headerValue"])

    dump, err := httputil.DumpRequestOut(req, true)
    fmt.Printf("%s", dump)
    if err != nil {
        log.Fatal("Error requesting dump")
    }

    return req, err
}

func getResponse(m map[string]string) (*http.Response, error) {
    req, err := newRequest(m)

    res, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    } else if res.StatusCode != 200 {
        return nil, fmt.Errorf("http status %d", res.StatusCode)
    }

    return res, err
}

そんでもってAPIの処理。(実際にはファイル別れてるけど便宜上まとめて書いてます)

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "time"
)

func main() {
    te := Remo("https://api.nature.global/1/devices")
}

func Remo(url string) (te float64) {
    res, err := encodeJson4Remo(url)
    if err != nil {
        log.Fatalf("http error: %v", err)
    }
    for _, r := range res {
        jst := time.FixedZone("Asia/Tokyo", 9*60*60)

        fmt.Println("Name: " + r.Name)
        fmt.Println("ID: " + r.ID)
        fmt.Printf("UpdatedAt: %s\n", r.UpdatedAt.In(jst).Format("2006/01/02 15:04:05"))
        fmt.Printf("TemperatureOffset: %d\n", r.TemperatureOffset)
        fmt.Println("User ID: " + r.Users[0].ID)
        fmt.Println("User Nickname: " + r.Users[0].Nickname)
        fmt.Printf("User Superuser: %t\n", r.Users[0].Superuser)
        fmt.Printf("Temperature val: %f\n", r.NewestEvents.Te.Val)
        te = r.NewestEvents.Te.Val
        fmt.Printf("Temperature CreatedAt: %s\n", r.NewestEvents.Te.CreatedAt.In(jst).Format("2006/01/02 15:04:05"))
    }

    return te
}

func encodeJson4Remo(url string) (RemoDevices, error) {
    token := "Bearer トークン入れてね"
    m := map[string]string{
        "url":         url,
        "headerKey":   "Authorization",
        "headerValue": token}
    res, err := getResponse(m)
    if err != nil {
        return remoDevices, err
    }

    byteArray, err := ioutil.ReadAll(res.Body)
    if err != nil {
        return remoDevices, err
    }
    defer res.Body.Close()

    if err := json.Unmarshal(byteArray, &remoDevices); err != nil {
        log.Fatalf("Error!: %v", err)
    }
    return remoDevices, err
}

結果。

$ go run natureremo_temperature.go remo.go httpclient.go 
GET /1/devices HTTP/1.1
Host: api.nature.global
User-Agent: Go-http-client/1.1
Authorization: Bearer トークン
Content-Type: application/json
Accept-Encoding: gzip

Name: 3F_Remo
ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
UpdatedAt: 2019/09/30 23:12:13
TemperatureOffset: -2
User ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
User Nickname: GOZU Kenichiro
User Superuser: true
Temperature val: 26.000000
Temperature CreatedAt: 2019/09/30 23:37:19
26.000000
success!

室温データの登録

室温は取れたのでInfluxDBに保存します。

環境構築

brewでinfluxdb入れます。

$ brew install influxdb
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
==> New Formulae
kepubify                                                    tmuxinator
==> Updated Formulae
ammonite-repl       evince              git-secret          jenkins-lts         libomp              squashfs

==> Downloading https://homebrew.bintray.com/bottles/influxdb-1.7.7.mojave.bottle.tar.gz
==> Downloading from https://akamai.bintray.com/aa/aa1cf675fa005f5f5dff6879ca4f5a31886f6d82828037c285656df9786f59c1?__g
######################################################################## 100.0%
==> Pouring influxdb-1.7.7.mojave.bottle.tar.gz
==> Caveats
To have launchd start influxdb now and restart at login:
  brew services start influxdb
Or, if you don't want/need a background service you can just run:
  influxd -config /usr/local/etc/influxdb.conf
==> Summary
🍺  /usr/local/Cellar/influxdb/1.7.7: 9 files, 127.4MB

$ influx
Failed to connect to http://localhost:8086: Get http://localhost:8086/ping: dial tcp [::1]:8086: connect: connection refused
Please check your connection settings and ensure 'influxd' is running.

落ち着け!起動してないとbrewが教えてくれてるじゃないかw

$ influxd -config /usr/local/etc/influxdb.conf

 8888888           .d888 888                   8888888b.  888888b.
   888            d88P"  888                   888  "Y88b 888  "88b
   888            888    888                   888    888 888  .88P
   888   88888b.  888888 888 888  888 888  888 888    888 8888888K.
   888   888 "88b 888    888 888  888  Y8bd8P' 888    888 888  "Y88b
   888   888  888 888    888 888  888   X88K   888    888 888    888
   888   888  888 888    888 Y88b 888 .d8""8b. 888  .d88P 888   d88P
 8888888 888  888 888    888  "Y88888 888  888 8888888P"  8888888P"

2019-09-30T17:50:06.740730Z info    InfluxDB starting   {"log_id": "0ICpYNa0000", "version": "v1.7.7", "branch": "master", "commit": "f8fdf652f348fc9980997fe1c972e2b79ddd13b0"}
2019-09-30T17:50:06.740759Z info    Go runtime  {"log_id": "0ICpYNa0000", "version": "go1.12.6", "maxprocs": 4}
2019-09-30T17:50:06.868211Z info    Using data dir  {"log_id": "0ICpYNa0000", "service": "store", "path": "/usr/local/var/influxdb/data"}
2019-09-30T17:50:06.868314Z info    Compaction settings {"log_id": "0ICpYNa0000", "service": "store", "max_concurrent_compactions": 2, "throughput_bytes_per_second": 50331648, "throughput_bytes_per_second_burst": 50331648}
2019-09-30T17:50:06.868349Z info    Open store (start)  {"log_id": "0ICpYNa0000", "service": "store", "trace_id": "0ICpYO50000", "op_name": "tsdb_open", "op_event": "start"}
2019-09-30T17:50:06.868582Z info    Open store (end)    {"log_id": "0ICpYNa0000", "service": "store", "trace_id": "0ICpYO50000", "op_name": "tsdb_open", "op_event": "end", "op_elapsed": "0.236ms"}
2019-09-30T17:50:06.868937Z info    Registered diagnostics client   {"log_id": "0ICpYNa0000", "service": "monitor", "name": "system"}

DB作ります。

$ influx
Connected to http://localhost:8086 version v1.7.7
InfluxDB shell version: v1.7.7
> show databases
name: databases
name
----
_internal
> create database remo
> show databases
name: databases
name
----
_internal
remo

アクセス用のユーザ作っておくべきなんですがちょいと手抜きで・・・

データ登録

参考にした記事では、"github.com/influxdata/influxdb/client/v2" を使えと書いてあったがgo getでエラーになる。URLが変わっているらしい。

import (
   "github.com/influxdata/influxdb/client/v2"
)

$ go get
package github.com/influxdata/influxdb/client/v2: cannot find package "github.com/influxdata/influxdb/client/v2" in any of:
    /usr/local/Cellar/go/1.13.1/libexec/src/github.com/influxdata/influxdb/client/v2 (from $GOROOT)
    /Users/gozu/go/src/github.com/influxdata/influxdb/client/v2 (from $GOPATH)

"github.com/influxdata/influxdb1-client/v2" に修正する。

Client作って tags と fields を登録する流れ。
値は複数登録できるっぽいけどとりあえず一回起動したら一回データを登録するようにしておいた。(cronで登録すればいいし)

package main

import (
    "fmt"
    "log"
    "time"

    "github.com/influxdata/influxdb1-client/v2"
)

func PushData(te float64) {
    MyDB := "remo"
    username := "root"
    password := "root"
    MyMeasurement := "temperature"

    c, err := client.NewHTTPClient(client.HTTPConfig{
        Addr:     "http://localhost:8086",
        Username: username,
        Password: password,
    })
    if err != nil {
        log.Fatalln("Error: ", err)
    }

    bp, err := client.NewBatchPoints(client.BatchPointsConfig{
        Database:  MyDB,
        Precision: "s",
    })
    if err != nil {
        log.Fatalln("Error: ", err)
    }

    tags := map[string]string{"remo": "3F"}
    fields := map[string]interface{}{
        "temperature": te,
    }

    pt, err := client.NewPoint(MyMeasurement, tags, fields, time.Now())
    if err != nil {
        log.Fatalln("Error: ", err)
    }

    bp.AddPoint(pt)
    err = c.Write(bp)
    if err != nil {
        log.Fatalf("Unable to write value. %v", err)
    }

    fmt.Printf("success!\n")
}

実行してみる。

$ go run remo.go remoget.go httpclient.go influxdb.go

確認。

$ influx
Connected to http://localhost:8086 version v1.7.7
InfluxDB shell version: v1.7.7
> use remo
Using database remo
> show series
key
---
temperature,remo=3F
> select * from temperature;
name: temperature
time                remo temperature
----                ---- -----------
1569869753000000000 3F   26
> precision rfc3339
> select * from temperature;
name: temperature
time                 remo temperature
----                 ---- -----------
2019-09-30T18:55:53Z 3F   26
> 

入ってるね!
それではとりあえずcronで1分に1回実行するようにしてデータ取ってみる。

$ crontab -e
*/1 * * * * /Users/gozu/go/src/github.com/gozuk16/natureremo_temperature/natureremo_temperature > /dev/null 2>&1

まあ取れてる。

> select * from temperature;
name: temperature
time                 remo temperature
----                 ---- -----------
2019-09-30T18:55:53Z 3F   26
2019-09-30T19:25:02Z 3F   26
2019-09-30T19:26:01Z 3F   26
2019-09-30T19:27:02Z 3F   26
2019-09-30T19:28:01Z 3F   26
2019-09-30T19:29:01Z 3F   26
2019-09-30T19:30:02Z 3F   26
2019-09-30T23:09:01Z 3F   26.59
2019-09-30T23:10:01Z 3F   26.59
2019-09-30T23:16:01Z 3F   26.59
2019-09-30T23:17:01Z 3F   26.59
2019-09-30T23:18:01Z 3F   26.59
2019-09-30T23:45:01Z 3F   26.59
2019-09-30T23:46:01Z 3F   27.2
2019-10-01T00:04:02Z 3F   27.79
2019-10-01T00:05:01Z 3F   27.79
2019-10-01T00:06:01Z 3F   27.79
2019-10-01T00:07:01Z 3F   27.79
2019-10-01T00:11:01Z 3F   27.79
2019-10-01T00:13:47Z 3F   27.79
2019-10-01T00:13:48Z 3F   27.79
2019-10-01T00:14:01Z 3F   27.2
2019-10-01T00:15:01Z 3F   27.2
2019-10-01T00:16:01Z 3F   27.2
2019-10-01T00:17:01Z 3F   27.2
2019-10-01T00:18:01Z 3F   27.2
2019-10-01T00:24:01Z 3F   27.2
2019-10-01T00:25:01Z 3F   27.2
2019-10-01T00:26:01Z 3F   27.2
2019-10-01T00:27:01Z 3F   27.2
2019-10-01T00:28:01Z 3F   27.2
2019-10-01T01:05:01Z 3F   27.79
2019-10-01T01:06:01Z 3F   27.79
2019-10-01T01:07:01Z 3F   27.79
2019-10-01T01:11:01Z 3F   27.79
2019-10-01T01:24:01Z 3F   27.79
2019-10-01T01:25:01Z 3F   27.79
2019-10-01T03:17:02Z 3F   29.59
2019-10-01T03:18:01Z 3F   29.59
2019-10-01T03:19:01Z 3F   29.59
2019-10-01T03:20:01Z 3F   29.59
2019-10-01T03:21:01Z 3F   29.59
2019-10-01T03:22:01Z 3F   29.59

貼り付けたデータは適当に間引いてます。

とりあえずLTするために突貫で書いたので後でまとめなおそう・・・

.NET Core のNugetパッケージと中身のDLLのバージョンにGitのコミットIDを含める

だいぶ前に、.NET Framework でAssemblyInfoをいじってファイルバージョンを自動採番するというのをやったのですが、

gozuk16.hatenablog.com

最近は、.NET Coreのプロジェクトが始まってやり方が色々違うし、どうやらJavaでやってたようにGitのコミットIDを入れられそうだというので試してみました。 あと、最近はNexusリポジトリにNugetパッケージをアップしているのですが、開発中にファイル名が同じだとキャッシュが更新されないことがあったのでビルドのたびにファイル名を変えたいというのがあります。 合わせて中身のDllの製品バージョンも一緒にできるといいなーと。

tech.guitarrapc.com

ここのサイトでは、GitVersioningとGitInfoが紹介されていたのでまずはシンプルそうなGitInfoを試してみました。

プロジェクトを新規作成

>dotnet new console -o assemblyInfoTest

cd assemblyInfoTest

>dotnet run
Hello World!

プロジェクトにGitInfoを追加

www.nuget.org

>dotnet add package GitInfo --version 2.0.20

早速Gitの情報を取得してみます。

using System;
using System.Diagnostics;
using System.Reflection;

namespace assemblyInfoTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Console.WriteLine($"GitBranch: {ThisAssembly.Git.Branch}");
            Console.WriteLine($"GitCommit: {ThisAssembly.Git.Commit}");
            Console.WriteLine($"GitSha: {ThisAssembly.Git.Sha}");
            Console.WriteLine($"GitBaseVersionMajor: {ThisAssembly.Git.BaseVersion.Major}");
            Console.WriteLine($"GitBaseVersionMinor: {ThisAssembly.Git.BaseVersion.Minor}");
            Console.WriteLine($"GitBaseVersionPatch: {ThisAssembly.Git.BaseVersion.Patch}");
            Console.WriteLine($"GitCommits: {ThisAssembly.Git.Commits}");
            Console.WriteLine($"GitTag: {ThisAssembly.Git.Tag}");
            Console.WriteLine($"GitBaseTag: {ThisAssembly.Git.BaseTag}");
            Console.WriteLine($"GitSemVerMajor: {ThisAssembly.Git.SemVer.Major}");
            Console.WriteLine($"GitSemVerMinor: {ThisAssembly.Git.SemVer.Minor}");
            Console.WriteLine($"GitSemVerPatch: {ThisAssembly.Git.SemVer.Patch}");
            Console.WriteLine($"GitSemVerLabel: {ThisAssembly.Git.SemVer.Label}");
            Console.WriteLine($"GitSemVerDashLabel: {ThisAssembly.Git.SemVer.DashLabel}");
            Console.WriteLine($"GitSemVerSource: {ThisAssembly.Git.SemVer.Source}");
            Console.WriteLine($"GitIsDirty: {ThisAssembly.Git.IsDirty}");

            var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version;
            var fileVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
            var productVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion;
            Console.WriteLine($"assemblyVersion: {assemblyVersion}");
            Console.WriteLine($"fileVersion: {fileVersion}");
            Console.WriteLine($"productVersion: {productVersion}");
        }
    }
}
C:\home\gozu\projects\assemblyInfoTest>dotnet run
Hello World!
GitBranch: master
GitCommit: 5ac94b0
GitSha: 5ac94b013eef929d3b8a932247966bd5f9a87952
GitBaseVersionMajor: 0
GitBaseVersionMinor: 0
GitBaseVersionPatch: 0
GitCommits: 2
GitTag:
GitBaseTag:
GitSemVerMajor: 0
GitSemVerMinor: 0
GitSemVerPatch: 2
GitSemVerLabel:
GitSemVerDashLabel:
GitSemVerSource: Default
GitIsDirty: True
assemblyVersion: 1.0.0.0
fileVersion: 1.0.0.0
productVersion: 1.0.0

簡単に取れました。

Git情報を埋め込んでみる

以下を追加しました。

[assembly: AssemblyVersion(ThisAssembly.Git.BaseVersion.Major + "." + ThisAssembly.Git.BaseVersion.Minor + ThisAssembly.Git.BaseVersion.Patch)]
[assembly: AssemblyFileVersion(ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.SemVer.Patch)]
[assembly: AssemblyInformationalVersion(ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.SemVer.Patch + "-" +
    ThisAssembly.Git.Branch + "+" + ThisAssembly.Git.Commit)]
>dotnet run
obj\Debug\netcoreapp2.2\assemblyInfoTest.AssemblyInfo.cs(12,12): error CS0579: System.Reflection.AssemblyFileVersionAttribute' 属性が重複しています。 [C:\home\gozu\projects\assemblyInfoTest\assemblyInfoTest.csproj]
obj\Debug\netcoreapp2.2\assemblyInfoTest.AssemblyInfo.cs(13,12): error CS0579: System.Reflection.AssemblyInformationalVersionAttribute' 属性が重複しています。 [C:\home\gozu\projects\assemblyInfoTest\assemblyInfoTest.csproj]
obj\Debug\netcoreapp2.2\assemblyInfoTest.AssemblyInfo.cs(16,12): error CS0579: System.Reflection.AssemblyVersionAttribute' 属性が重複しています。 [C:\home\gozu\projects\assemblyInfoTest\assemblyInfoTest.csproj]

ビルドに失敗しました。ビルド エラーを修正して、もう一度実行してください。

.NET CoreだとAssemblyInfo.csが生成されるので書き換えたいプロパティの生成を抑制する必要があるらしい。

csprojに以下を追加しました。

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
    <GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
    <GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
  </PropertyGroup>
>dotnet run
Hello World!
GitBranch: master
GitCommit: 37fdbb4
GitSha: 37fdbb48f4c47b29089f6dd0ce36178c21500861
GitBaseVersionMajor: 0
GitBaseVersionMinor: 0
GitBaseVersionPatch: 0
GitCommits: 3
GitTag:
GitBaseTag:
GitSemVerMajor: 0
GitSemVerMinor: 0
GitSemVerPatch: 3
GitSemVerLabel:
GitSemVerDashLabel:
GitSemVerSource: Default
GitIsDirty: False
assemblyVersion: 0.0.0.0
fileVersion: 0.0.3
productVersion: 0.0.3-master+37fdbb4

おー、出来た!

CIで外部から指定できるように

想定ではJenkinsでnupkgのファイル名とDllの製品バージョンをビルドごとに一意なバージョンにするなので、GitのコミットIDを付加する方向で行きます。

外部から指定できるようにcsprojに Version を定義しておきます。 1.0.0としてますが書き換わるので気にしない。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
    <!--GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute-->
    <!--GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute-->
    <Version>1.0.0</Version>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="GitInfo" Version="2.0.20">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>

</Project>

コードの方も調整します。 FileVersionだけは入れておこうかという感じです。

using System;
using System.Diagnostics;
using System.Reflection;

//[assembly: AssemblyVersion(ThisAssembly.Git.BaseVersion.Major + "." + ThisAssembly.Git.BaseVersion.Minor + ThisAssembly.Git.BaseVersion.Patch)]
[assembly: AssemblyFileVersion(ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.SemVer.Patch)]
//[assembly: AssemblyInformationalVersion("1.0.0" + "-alpha." + ThisAssembly.Git.Commit)]

namespace assemblyInfoTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Console.WriteLine($"GitBranch: {ThisAssembly.Git.Branch}");
            Console.WriteLine($"GitCommit: {ThisAssembly.Git.Commit}");
            Console.WriteLine($"GitSha: {ThisAssembly.Git.Sha}");
            Console.WriteLine($"GitBaseVersionMajor: {ThisAssembly.Git.BaseVersion.Major}");
            Console.WriteLine($"GitBaseVersionMinor: {ThisAssembly.Git.BaseVersion.Minor}");
            Console.WriteLine($"GitBaseVersionPatch: {ThisAssembly.Git.BaseVersion.Patch}");
            Console.WriteLine($"GitCommits: {ThisAssembly.Git.Commits}");
            Console.WriteLine($"GitTag: {ThisAssembly.Git.Tag}");
            Console.WriteLine($"GitBaseTag: {ThisAssembly.Git.BaseTag}");
            Console.WriteLine($"GitSemVerMajor: {ThisAssembly.Git.SemVer.Major}");
            Console.WriteLine($"GitSemVerMinor: {ThisAssembly.Git.SemVer.Minor}");
            Console.WriteLine($"GitSemVerPatch: {ThisAssembly.Git.SemVer.Patch}");
            Console.WriteLine($"GitSemVerLabel: {ThisAssembly.Git.SemVer.Label}");
            Console.WriteLine($"GitSemVerDashLabel: {ThisAssembly.Git.SemVer.DashLabel}");
            Console.WriteLine($"GitSemVerSource: {ThisAssembly.Git.SemVer.Source}");
            Console.WriteLine($"GitIsDirty: {ThisAssembly.Git.IsDirty}");

            var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version;
            var fileVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
            var productVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion;
            Console.WriteLine($"assemblyVersion: {assemblyVersion}");
            Console.WriteLine($"fileVersion: {fileVersion}");
            Console.WriteLine($"productVersion: {productVersion}");

        }
    }
}

Jenkinsfileで以下のようにdotnetコマンドを実行してビルドとパッケージングをします。

bat """chcp 65001

for /f "usebackq tokens=*" %%a in (`git rev-parse --short HEAD`) do @set GIT_COMMIT=%%a
for /f "usebackq tokens=*" %%a in (`git rev-parse --abbrev-ref HEAD`) do @set GIT_BRANCH=%%a

dotnet build -p:Version="1.0.0-alpha.%GIT_COMMIT%"
dotnet pack --no-build -p:PackageVersion="1.0.0-alpha.%GIT_COMMIT%"
"""

これで、製品バージョンに 1.0.0-alpha.xxxxxxx と埋め込まれたDllを含めた、 assemblyInfoTest.1.0.0-alpha.xxxxxxx.nupkg が出来ました。

1.0.0-aplpha のところはまあどこかで一元的に定義しないといけないですけどね。

Karabiner-ElementsでMavVimだけ特定のキーマップを除外する

MacにHHKBを付けているので、Karabiner-Elementsにいつもお世話になってます。

仕事ではMacWindowsを同時に使うのでMacWindowsキーバインドをある程度あわせたくて、Complex Modificationsに PC-Style Shortcuts の中から PC-Style Copy/Paste/Cut を追加しています。

ところでVimで矩形選択をたまにしたくなるのですが「Ctrl + v」がペーストになってしまいます。
iTerm2の中でVim使ってるときはうまくいくのだからこれはなにかの設定に違いない。。。

ぐぐるfrontmost_application_unless を設定しなさいということらしい。

qiita.com

~/.config/karabiner/assets/complex_modifications/ を探ると 1543454397.jsonPC-Style Shortcuts のようです。
※ このファイル名、別のMacで見てみたら違ってました。

「Ctrl + v」を探すと

        {
          "type": "basic",
          "from": {
            "key_code": "v",
            "modifiers": {
              "mandatory": [
                "control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "v",
              "modifiers": [
                "left_command"
              ]
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "^com\\.microsoft\\.rdc$",
                "^com\\.microsoft\\.rdc\\.mac$",
                "^com\\.microsoft\\.rdc\\.macos$",
                "^com\\.microsoft\\.rdc\\.osx\\.beta$",
                "^net\\.sf\\.cord$",
                "^com\\.thinomenon\\.RemoteDesktopConnection$",
                "^com\\.itap-mobile\\.qmote$",
                "^com\\.nulana\\.remotixmac$",
                "^com\\.p5sys\\.jump\\.mac\\.viewer$",
                "^com\\.p5sys\\.jump\\.mac\\.viewer\\.web$",
                "^com\\.teamviewer\\.TeamViewer$",
                "^com\\.vmware\\.horizon$",
                "^com\\.2X\\.Client\\.Mac$",
                "^com\\.vmware\\.fusion$",
                "^com\\.vmware\\.horizon$",
                "^com\\.vmware\\.view$",
                "^com\\.parallels\\.desktop$",
                "^com\\.parallels\\.vm$",
                "^com\\.parallels\\.desktop\\.console$",
                "^org\\.virtualbox\\.app\\.VirtualBoxVM$",
                "^com\\.vmware\\.proxyApp\\.",
                "^com\\.parallels\\.winapp\\.",
                "^com\\.apple\\.Terminal$",
                "^com\\.googlecode\\.iterm2$",
                "^co\\.zeit\\.hyperterm$",
                "^co\\.zeit\\.hyper$",
                "^io\\.alacritty$",
                "^net\\.kovidgoyal\\.kitty$"
              ]
            }

とありました。やっぱり iterm2 設定されてる。

ここにMacVimを追加しよう! Karabiner-ElementsのEvenViewerを立ち上げて、「Frontmost Application」にしてからMacVimを触ると

Bundle Identifier:  org.vim.MacVim
File Path:          /Applications/MacVim.app/Contents/MacOS/MacVim

と出ます。 ということで↑の bundle_identifiers

                "^org\\.vim\\.MacVim$"

を追加します。

これでKarabiner-Elementsを再起動すればいいと思ったのですがどうも反映しない。
Complex Modificationsで PC-Style Copy/Paste/Cut をremoveしてからadd ruleしてみたら反映した!

f:id:gozuk16:20190830184916p:plain

やっと出来たー。

MacからWindowsファイル共有(日本語で濁点あり)をマウントする

ちょっとターミナルでやりたかったので調べたら今はmountコマンドでやるらしい。

$ mount -t smbfs //username:passwd@hostname/hogehoge ~/mnt/hogehoge

↑これは普通にできる。

で実際に、共有したい所を見てみたら日本語じゃないか。嫌な予感・・・。

$ mount -t smbfs //username:passwd@hostname/ほげほげ ~/mnt/hogehoge
mount_smbfs: URL parsing failed, please correct the URL and try again: Invalid argument

やっぱりね。 _| ̄|○
調べてみたら日本語というより濁点がいけないらしい。

qiita.com

URLエンコードしとけばいいとのことなので、

$ mount -t smbfs //username:passwd@hostname/%E3%81%BB%E3%81%92%E3%81%BB%E3%81%92 ~/mnt/hogehoge

できたー。 (^^)

Google Domains で .dev ドメイン取得

購入

Googleが .dev ドメインを開放していくつかのレジストラで取得できるようになったらしいですが、とりあえず一番安そうな Google Domainsで取ってみました。 初日ほど混んでなくて普通にドメイン検索できました。

まずは、

domains.google

で、hogehoge.dev とかなんとか検索すると値段が出ます。 メジャーじゃない名前やで4文字以上ならだいたい1,400円/年で出てくるようです。 有名な人や企業、地名の名前や辞書に載っているような単語は結構高いものもあるようです。 (ちなみにhogehoge.devはUnavailableでした)

気に入ればカートに入れて購入します。 デフォルトで自動更新になっているようです。(とりあえず自動更新にしておきました)

購入に進むと、名前と住所などを入れるフォームが出てきます。 とりあえず日本語はやめておいて英語っぽく半角英数で入れておきました。

次に進むと、Google Payで支払うかと聞いてきます。 どうやらブラウザでログインしていたGoogleアカウントと自動的に紐づけたようです。 ここでポチッと押すとメールが飛んできて、メールの中の「メールアドレスを確認」を押せば購入完了です。

そういえばドメインを買うときは英語しかなさそうでしたけど、買った後でGoogle Domainsにログインして管理するところは右下の言語設定を日本語にすると切り替わりました。
最初開くと日本語になってるけど、あえて日本語をもう一度選んでから保存すると切り替わりましたw

名前解決

もともとGCEの無料インスタンスで遊んでいたついでに.devドメインの事を思い出したので、名前解決ができるようにします。

固定IP設定

普通にGCEのインスタンスを作成すると外部IPがエフェメラルになってます。
まずは固定IPにしちゃいます。
手順は、

  1. インスタンスを選択
  2. 編集
  3. ネットワーク インターフェース > 外部IP > IPアドレスを作成

です。
これで名前をつければ外向けの固定IPが確保できます。

ネットワークサービス階層
 プレミアム (現在のプロジェクトレベル階層、変更)
 標準

という選択肢があるのですが、無料でやりたいがゆえに標準にしてしまうと割り当てるインスタンスと階層が違うとかなんとか言われてうまくいきません。
とりあえずプレミアムでいいっぽいです。
私は一度消して作り直しました。

f:id:gozuk16:20190306232737p:plain

作成した固定IPが外部IPに出ていればおけ。

f:id:gozuk16:20190306233040p:plain

外部IPを予約して使わないと課金されるのでいらなくなったら消すのを忘れずに。
だいぶ昔の記憶ですがAWSもそんな感じでしたね。

DNS設定

ドメインもIPも準備できたら名前解決しよう。
Google DomainsでもCloud DNSでも出来そうですが、今回はCloud DNSでやりました。(なんとなく)

  1. Cloud DNSドメインの設定 GCPのメニューから、ネットワーキング > ネットワークサービス > Cloud DNSを選択。
    ゾーンの作成からソーン名と自分が取ったドメイン名を入れます。
    DNSSECはデフォルトオフだったのでそのまま。(よく知らないのでちゃんと調べてからでもいいかな)
    これでNSレコードができます。
    f:id:gozuk16:20190306233316p:plain
    4つのDNSサーバが登録されているので控えておきます。
    f:id:gozuk16:20190306233516p:plain
  2. Google Domainsの設定 Google Domainsにログインすると管理画面に入れます。
    ドメインを選ぶと右のメニューにDNSとあるのでここを開いて、ネームサーバーの設定をします。
    デフォルトでGoogleのネームサーバが設定されているのですが↑で作ったNSレコードのサーバと違うので、カスタムネームサーバーを使用するにして登録します。(同じならもちろん何もする必要ないですけど) f:id:gozuk16:20190306233620p:plain
  3. サブドメインの設定 Cloud DNSに戻って、ゾーン > 作ったゾーンを選択 > ゾーンの詳細 > レコードセットを追加 します。
    ドメイン名でAレコード作ります。
    DNS名:example.dev
    リソースレコードのタイプ:A
    IPv4アドレス:先程作った固定IP
    にします。TTLはデフォルトの5分のまま作りましたが、落ち着いたら伸ばしておけばよいかも。
    後はとりあえずwwwだけ登録。
    DNS名:www.example.dev
    リソースレコードのタイプ:CNAME
    正規名:example.dev
    です。
  4. 確認 TTL 5分なので、5分たったら確認です。
    自分のMac
$ dig www.example.dev

;; ANSWER SECTION:
www.example.dev.        300 IN  CNAME   example.dev.
example.dev.        300 IN  A   xx.xx.xx.xx

おー、引けてる!
こりゃ簡単だ〜。
bindのコンパイルからやってた時代にとあるコミュニティのサーバ管理やってたけど隔世の感あるなぁ。
クラウド素晴らしい。

あとは

SSHを別ポートであげるのはやった。
devドメインhttpsじゃないといけないので証明書を設定しなきゃだけど、Google-managed SSL certificateってものがあって中身はLet's Encriptらしい。
ロードバランサー(負荷分散)から登録するということらしいのでまだ試してないが無料の範囲でできるならやってみようかな。それともLet's Encriptを自動更新するようにしたほうがいいか。

まあ、また時間が取れたら考えよ。