tech.guitarrapc.cóm

Technical updates

パスワード管理をTeamsId から Bitwarden に移行した

今のパスワード管理に小さな不満があるので長年次のパスワード管理をさがしていたのですが、Bitwardenが今ある全ての望みをかなえてくれました。

bitwarden.com

今回、TeamsId から Bitwarden に全面移行したのでその移行についてメモをしておきます。

目次

今まで使ってきたパスワード管理

個人のパスワード管理は、2015年3月まではMeldiumを使ってましたが Discontinueが発表されてからはTeamsId を使っていました。

www.teamsid.com

TeamsId を見返す

TeamsId (運営はSplashData) の良いと感じてる点は次の通りで多くの面で満足していました価格は$3user/month です。

  • Web上で管理される
  • 少人数でのパスワード共有も管理が容易
  • Chrome拡張があり入力が容易
  • iPhoneアプリもある
  • Googleログインで統制できる
  • G Suite の9 squareに表示できる

一方で、3年つかっていると細かい不満がたまってきました。

  • Web管理画面で描画が遅い
  • 反映までの3-5secの間に他のレコードを触ると意図と異なったレコードを編集することがある
  • 利用者の声が他に全然おらずサービス終わることありそうという気配
  • 日本語サービス名にすると全レコードが表示されなくなる
  • サポートの対応が技術的に掘り下げ弱い

特に日本語サービス名にした時に表示されなくなるのはかなり焦るものがあり、半年前に3度目が発生し改善の見込みが見えないので乗り換えを考えていました。

機能数よりは、少人数での共有のしやすさ、使い勝手の良さを評価しています。

www.itqlick.com

検討したサービス

いくつか検討し、仕事でも使ったりすることで探ってきました。

1Password、LastPass、KeePass、Zohoいずれも使い勝手と価格と少人数での共有の面からあまり満足できるものはなく悩みが多かったです。ちょうど1Password Teams が発表されたタイミングもあり触っていたのですが結局TeamsId でいいやとなっていました。

1password.com

www.lastpass.com

ここ1年は、Dashlane がワンチャンかと思っていましたが、決定的な理由がないのでペンディングしていました。 お高めですし。

無料パスワードマネージャー | Dashlane

やりたいこととのバランス、使い勝手はそこまで変わらないなら乗り換えないというのは自分の行動を見ていてもそういうものかなぁと思います。個人的には、クラウド管理でもいいと思っており、ローカル/自前クラウド管理は避けたいところです。(必要ならやりますが必然性は感じていない)

Bitwarden

Bitwardenはたびたび見ていましたが、あまり興味がでなかったものの2018年末に改めて良いという話を聞いて試しました。

bitwarden.com

オープンソースなのは結構良いと思っているのですが、妙な処理があっても全コードは見てられないので気付けるかなぁと思いつつ。しかし、最悪コード読めるのはいいと感じています。 それでも不安なら kubernetes クラスタ組めばいいと思います。

2FA して2人で共有できればいいので、PersonalもBusinessも無料で十分満たせそうです。 $5 + $2(user) でも十分安いので、課金しています。

YubiKeyに移行も進めているので、Personal だけ Premium もあり得ます。

ブラウザ拡張、スマホアプリ共に十分使い勝手は良く感じます。

Bitwardenの使い方

これは多くの記事があるのであえて書くことはないと感じるのでそちらをどうぞ。

tips4life.me

excesssecurity.com

TeamsIdからの移行

ぱっと使ってみた感じはよさそうなので、今のTeamsId のレコードを移行します。残念ながら TeamsId はありません、同一会社のSpashIdがあるのに。

試しにTeamsIdのエキスポートcsvをそのまま取り込んでみると、すさまじいことになったので推奨できません。Bitwarden のフォルダがまともに消せなくていやになりそうになったのは内緒です。

仕方ないので、Bitwarden の Generic Importフォーマットで取り込みを試みます。ドキュメントではcsv とあります。

しかし、Fields は入れ子のレコードでExportしてみると大変なことになるのが分かります。

folder,favorite,type,name,notes,fields,login_uri,login_username,login_password,login_totp
Social,1,login,Twitter,,,twitter.com,me@example.com,password123,
,,login,My Bank,Bank PIN is 1234,"PIN: 1234
Question 1: Blue",https://www.wellsfargo.com/home.jhtml,john.smith,password123456,
,,login,EVGA,,,https://www.evga.com/support/login.asp,hello@bitwarden.com,fakepassword,TOTPSEED123
,,note,My Note,"This is a secure note.

Notes can span multiple lines.",,,,,

Csvは行を跨ぐ、入れ子データの取り扱いが面倒なので避けたいところです。 ドキュメントにはありませんが、jsonでインポートできます。

{
  "folders": [],
  "items": [
    {
      "id": "2fb7acc2-41dc-4659-9dc3-a9d301283339",
      "organizationId": null,
      "folderId": null,
      "type": 3,
      "name": "card sample",
      "notes": "note line 1",
      "favorite": false,
      "card": {
        "cardholderName": "Jane Doe",
        "brand": "Visa",
        "number": "123456778",
        "expMonth": "1",
        "expYear": "2021",
        "code": "1234"
      },
      "collectionIds": null
    },
    {
      "id": "5a9eb019-e092-43ae-ac30-a9d301279b2a",
      "organizationId": null,
      "folderId": null,
      "type": 1,
      "name": "LoginSample",
      "notes": "note line 1\nnote line 2",
      "favorite": false,
      "login": {
        "uris": [
          {
            "match": null,
            "uri": "https://example.com"
          }
        ],
        "username": "username",
        "password": "password",
        "totp": "authenticator key"
      },
      "collectionIds": null
    },
    {
      "id": "254a958e-dc04-4330-aa05-a9d3012801c0",
      "organizationId": null,
      "folderId": null,
      "type": 1,
      "name": "LoginSample2",
      "notes": null,
      "favorite": false,
      "fields": [
        {
          "name": "custom",
          "value": "value",
          "type": 0
        },
        {
          "name": "custom hiden",
          "value": "value",
          "type": 1
        },
        {
          "name": "custom boolean",
          "value": "true",
          "type": 2
        }
      ],
      "login": {
        "uris": [
          {
            "match": null,
            "uri": "https://example.com"
          }
        ],
        "username": "username",
        "password": "password",
        "totp": "authenticaticator key"
      },
      "collectionIds": null
    }
  ]
}

こちらは素直なフォーマットで型変換も容易です。

ということで、TeamsId Csv -> Bitwarden Json への変換を書きましょう。

下準備

TeamsId は、Export結果にタグは含みません。あくまでもフィールドとしてすべて出てくるので、Bitwardenに取り込みたい項目はFieldに割り当てておきます。

私の場合は、Bitwarden Personalに取り込む場合は、TeamsId でGroupフィールドを作って割り当てたいグループ名をいれました。

実装

パーサーライブラリとして公開しました。*1

github.com

TeamsId のPersonalデータ -> Bitwarden の Personal データ変換、TeamsId の Organizationデータ -> Bitwarden の Organizationデータの両方に対応しています。

Dockerfile、.NET Coreも対応済みです。使い方はSampleを用意したのでどうぞ。

github.com

これに限らずですが、作業はLinqPad でやっていたので C# です。こういうパーサーを書くとき、LinqPadは非常に便利です。

変換結果を見ながら書き換えできます。*2

実装のポイントだけメモしておきます。

TeamsId Csv の解析

C# で Csv パーサーを探していくつか試しましたが、結果 CsvHealper が最も使いやすかったです。AutoMapperもあり、改行レコードがあっても素直に処理してくれます。.NETStandard も対応しています。

github.com

joshclose.github.io

TeamsId はカスタムフィールドがあるとどんどんExport時のカラムが増えるので、40フィールドのマッピングをするのはつらいのでAutoMapperはほしいところです。さて、利用に際してはStreamで読み込みが必要なので軽いラッパーだけ用意しました。

public class CsvParser
{
    private readonly string path;

    public CsvParser(string path)
    {
        this.path = path;
    }

    public T[] Parse<T>() where T : class
    {
        using (var reader = new StreamReader(path))
        using (var csv = new CsvReader(reader))
        {
            csv.Configuration.MissingFieldFound = null;
            var records = csv.GetRecords<T>();
            return records.ToArray();
        }
    }
}

TeamsId のフィールド解析、値取得

TeamsId のExport Csv は、次のフォーマットになります。

description,note,Field0,Type0,Value0,Field1,Type1,Value1,Field2,Type2,Value2,Field3,Type3,Value3,Field4,Type4,Value4,Field5,Type5,Value5,Field6,Type6,Value6,Field7,Type7,Value7,Field8,Type8,Value8,Field9,Type9,Value9,Field10,Type10,Value10,Field11,Type11,Value11,Field12,Type12,Value12,Field13,Type13,Value13,Field14,Type14,Value14,Field15,Type15,Value15

description、note以外は、レコード1つあたり FieldN, TypeN, ValueN の連続です。そのため、今回はリフレクションしてフィールドの値から型にマッピングします。

C# 7 から使えるswitch でのwhen 句を用いることで、強力に条件分岐ができるので、フィールドを特定のプロパティに割り当てたい時に便利でした。

    private FieldRecord ParseFieldRecord(PropertyInfo[] props, Type t, TeamsIdDefinition source)
    {
        var fieldRecord = new FieldRecord();
        // get FieldXX properties via reflection
        var fieldRecords = props
            .Where(x => Match(x.Name, @"Field\d+"))
            .Where(x => GetPropertyValue(source, t, x.Name) != "")
            .Select(x => (key: GetPropertyValue(source, t, x.Name), value: GetPropertyValue(source, t, x.Name.Replace("Field", "Value"))))
            .ToArray();

        // get field's value and categolize each via field name regex pattern
        var secureMemoList = new List<(string, string)>();
        var memoList = new List<(string, string)>();
        foreach (var record in fieldRecords)
        {
            switch (record.key)
            {
                case var _ when Match(record.key, "url") && fieldRecord.Url == null:
                    fieldRecord.Url = record.value;
                    break;
                case var _ when Match(record.key, "email|e-mail") && fieldRecord.Email == null:
                    fieldRecord.Email = record.value;
                    break;
                case var _ when Match(record.key, "username") && fieldRecord.UserName == null:
                    fieldRecord.UserName = record.value;
                    break;
                case var _ when Match(record.key, "password") && fieldRecord.Password == null:
                    fieldRecord.Password = record.value;
                    break;
                case var _ when Match(record.key, "pass|access|secret|pin|token"):
                    secureMemoList.Add((record.key, record.value));
                    break;
                case var _ when Match(record.key, "group"):
                    fieldRecord.Group = record.value;
                    break;
                default:
                    memoList.Add((record.key, record.value));
                    break;
            }
        }
        fieldRecord.Fields = memoList.ToArray();
        fieldRecord.SecureFields = secureMemoList.ToArray();
        return fieldRecord;
    }

    private bool Match(string text, string pattern)
    {
        return Regex.IsMatch(text, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
    }

    private string GetPropertyValue(TeamsIdDefinition record, Type t, string propertyField)
    {
        return (string)t.GetProperty(propertyField).GetValue(record);
    }

手元の300レコードは素直にインポートできたので、だいたいのケースではつかえそうです。

変換時の注意

Bitwarden Personal では FolderId が GUIDで定義、マッピングしておかないと死にます。(実装は対応済みで、Sampleに定義例があります。)

Bitwarden Organizationでは、OrganizationIdとCollectionIds を定義してマッピングしましょう。(実装は対応済みで、Sampleに定義例があります。)

インポート

変換した結果はJsonで出力されるのでBitwarden で取り込めば完了です!

まとめ

幸せな感じなので少し様子を見てみましょう。

*1:TeamsIdの利用者数からして需要はなさそう

*2:黒字で塗りつぶしたらさっぱりわからない

はてなブログを https 対応するためにmixed content を検知する MixedContentCheckerを作った

前回の記事でhttps 化の前段階として、はてなブログの全URLを取得しました。

tech.guitarrapc.com

https化を有効にすると、mixed content が出るようになるので有効にします。

あとは、https 化したページに httpコンテンツが混じっている時に起こる、mixed content 警告一覧を取得して直してみましょう。

なお、直すのは手作業です。

目次

TL;DR;

dockerでchrome ヘッドレスをSeleniumで動かして、logからmixed content があるか検知してログ出力する。

検知したページは、3パターンの修正のいずれかをちまちまかけていく。

環境

今回は、PowerShell と C# で実装しました。golang は実装中。

いずれの環境も、docker で ubuntu 18.04 環境で実行します。

  • PowerShell : mcr.microsoft.com/powershell:ubuntu-xenial
  • C# : microsoft/dotnet:2.2-runtime-bionic
  • golang : 未実装

処理の流れ

どの言語も変わらず、chrome のログから mixed content を取得します。

  • URLにchrome headlessで自動アクセスする
  • chrome driver を初期化
  • 指定したURLにアクセス
  • ログから mixed content をフィルター
  • 該当ログがあれば markdown テーブルフォーマットで出力

実装

リポジトリおいておきます。

github.com

実行方法は、READMEをみてください。

MixedContentChecker/README.md at master · guitarrapc/MixedContentChecker · GitHub

ローカル実行もできますが、Dockerで動かすことで、手元にSeleniumやChromeヘッドレスドライバーを用意したり、環境初期化で困ったりすることを割けます。

こういうのをローカルで動かす意味はあんまりないので、Dockerで動かすのがいいでしょう。

PowerShell

Dockerfileです。特に何も気にせず、粛々とchrome headless + chrome driver + selenium をいれます。

gist.github.com

前回のサイトマップから全URLを取得するスクリプトを呼び出しつつ、Chrome Driver + Selenium処理を書きます。

gist.github.com

ポイントは、chrome driverに渡した引数 "--no-sandbox" です。

C# では起こらないのですが、PowerShellからChrome ヘッドレスを実行するときは、--no-sandbox がないと実行できないようです。(はまった)

CSharp

Dockerfileです。先ほどと違い、C# のビルド時にselenium + chrome driverは入るので、chrome driverだけ入れます。

gist.github.com

先ほどのPowerShellと異なり、Parallel.Foreachdによる並列処理を用意しました。

gist.github.com

記事が480以上あるため、1つ1つにアクセスしていると終わりません。単純に1ページ5秒としても、2400sec (40min) かかります。実際は、OneDrive の写真埋め込みページなどで60secかかったりしていたのでもっとです。

おおよそCPUコア数で並列がいいのですが、Docker内部への割り当てしだいです。今回は、15並列で不安定になり10並列で安定したのでデフォルト値にしています。Docker実行時にパラーメータを変えたいので、環境変数から値を渡せるようにしています。*1

10並列で、10分ぐらいでおわるようになったのでだいたいこれぐらい感があります。

あとは、先ほど同様にSeleniumでchrome headlessを動かすだけです。

処理の全体はリポジトリをみてください。

MixedContentChecker/Program.cs at master · guitarrapc/MixedContentChecker · GitHub

Golang

[TBD]

修正

3つのパターンで修正します。

  • httpをhttps にする
  • 存在しないURLを消す
  • はてなフォトがembedded記法なら記事の保存しなおし
  • Google Web Master をhttpsで取り直し

http -httpsへの置き換え

心を無にしてやりました。 はてなブログにAPIで取得、保存しなおしはちょっと面倒感があります。

記事を全コピー、vscodeで置換、貼り付け直して保存です。サシミタンポポ

また、デザインページもhttp -> https が必要です。私の場合は、Google フォントと外部cssなどでしたが、ついでにptengine やzenback 当りが邪魔をしていたので外しました。zenbackははてなブログで関連記事機能があるので不要になってました。

github.com

存在しないURLを消す

特にOneDrive のimage埋め込みと2013ぐらいの古い記事でした。

OneDriveは、生imageがOneDriveにあったものははてなフォトにいったんおいています。OneDriveのimage埋め込みは、以前コメントでも指摘受けていたので、もう二度とつかわないでしょう。

存在しないURLは404で取得できるので消しました。

はてなフォト

はてなフォトは、生urlでなくはてな記法によるid埋め込みの場合、記事を保存しなおすことでhttps化されます。 粛々と記事を保存しなおします。サシミタンポポ

help.hatenablog.com

結果

1122 + 296 + 70 + 22 + 187 なので、1697件だったようです。

sample/logs においておきました。

MixedContentChecker/samples/logs at master · guitarrapc/MixedContentChecker · GitHub

手作業なら死んでました。修正は手作業なのでもうやりたくないです。

あと、セキュリティ警告でページ表示できない状況も直ったようです、すいませんでした。

まとめ

golang 書いてから上げようと思いましたが、やってすでに1週間立つのでとりあえず記事にしていくスタイルで。

*1:Docker実行だと、引数より環境変数の方が素直で扱いやすくて好みです。

はてなブログの全エントリーURLを取得する

このブログ、実はhttpのままです。 はてなブログをやめるか考えているのですが、いったんhttps対応を進めましょう。

困るのがmixed content なのですが、とっかかりとしてこのブログの全URLを取得します。

目次

sitemap の取得

全記事のURLを取得するときに考えるのが、Google Web Master でどうやってクローラーにヒントを出しているかです。ご存知の通り、こういう時に使うのがサイトマップです。

ということで、全記事のURL取得は安直にサイトマップから辿ればいいでしょう。

はてなブログのsitemap は、ブログURL + /sitemap.xml でpagenation付sitemapindexを取得できます。 このブログならこのような感じです。

<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap>
    <loc>http://tech.guitarrapc.com/sitemap.xml?page=1</loc>
    <lastmod>2019-01-06</lastmod>
  </sitemap>
  <sitemap>
    <loc>http://tech.guitarrapc.com/sitemap.xml?page=2</loc>
    <lastmod>2019-01-06</lastmod>
  </sitemap>
  <sitemap>
    <loc>http://tech.guitarrapc.com/sitemap.xml?page=3</loc>
    <lastmod>2019-01-06</lastmod>
  </sitemap>
  <sitemap>
    <loc>http://tech.guitarrapc.com/sitemap.xml?page=4</loc>
    <lastmod>2019-01-06</lastmod>
  </sitemap>
  <sitemap>
    <loc>http://tech.guitarrapc.com/sitemap.xml?page=5</loc>
    <lastmod>2019-01-06</lastmod>
  </sitemap>
</sitemapindex>

あとは、各sitemapごとにアクセスして、記事URLを拾うだけです。

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>http://tech.guitarrapc.com/entry/2015/11/19/030028</loc>
    <lastmod>2015-11-19</lastmod>
  </url>
  <url>
    <loc>http://tech.guitarrapc.com/entry/2015/11/11/032544</loc>
    <lastmod>2016-09-24</lastmod>
  </url>
   <!-- ..... continue -->
</urlset>

結果がurl一覧で取れればいい感じに使えそうです。

http://tech.guitarrapc.com/ 
http://tech.guitarrapc.com/about 
http://tech.guitarrapc.com/entry/2019/01/05/060326 
http://tech.guitarrapc.com/entry/2019/01/05/044741 
http://tech.guitarrapc.com/entry/2018/12/22/235927 
http://tech.guitarrapc.com/entry/2018/09/29/165215 
http://tech.guitarrapc.com/entry/2018/09/29/154004 
http://tech.guitarrapc.com/entry/2018/09/29/151114 
..... continue

sitemap は所定のフォーマットに沿っているxmlなので適当に処理します。

今回はPowerShell、C#、Golang それぞれで書いてリポジトリにおいておきました。順にみていきます。

github.com

PowerShell

PowerShell 6.0以降 で動作します。(5.0で動かす場合は、Invoke-WebRequest-UseBasicParsing スイッチを追加するといいでしょう)

gist.github.com

特別に難しいことはないのですが、PowerShellの場合はXML型が担保できれば要素名をキーとして辿ることができます。 nullの取り扱いがゆるいこともあり、シンプルに書けます。

    [xml]$index = $res.Content
    $sitemaps = $index.sitemapindex.sitemap.loc

CSharp

C# 7.3 w/.NET Core 2.2 で動作します。*1

gist.github.com

C# でXMLだと、namespaceを毎度指定することになるのですこし面倒な感じがあります。

XNamespace ns = "http://www.sitemaps.org/schemas/sitemap/0.9";
var sitemaps = XElement.Parse(res).Descendants(ns + "loc").Select(x => x.Value).ToArray();

Golang

go-sitemapを使うとシンプルになります。

github.com

gist.github.com

練習なので、既存のパッケージを使わずstructを定義して、xmlのUnmarshal で割り当ててみます。

gist.github.com

安直に書いてみたのですが、こんな感じなのでしょうか? slice で要素数が事前にわからないので拡張に任せるのがいやなのと、要素確保をした場合に最初のappendで無駄になるのはどうするといいのかなぁ。

まとめ

とりあえず全ブログ記事URLが取れたので、次はmixed content の警告です。

*1:C# 8.0 / .NET Core 3.0 でも動作します。

2018年を振り返って

2019年になって4日経過しました。

2017年の振り返りに続いて、2018年の振り返りをしてみます。

tech.guitarrapc.com

目次

総合

2018年の初めにこう書きました。

流れは年末から変わらず。C# / Swift / TypeScript / Serverless / Container がメインです。やること多いので、いっこずつ片付けます。 やりこんでできないより、やりきるは何より大事です。できるを最低条件に過程をよくするのは力量です。自分でハードルを挙げておいて、工夫してよじ登るのが続くのでしょう。 インフラ、サーバーサイド、クライアントサイドの視点からどうやるといいのかを考えられるようになったのが2017年の最大の獲得であり、今後試されます。

2018年はクライアントとサーバーサイドの両方をやっており、アーキテクチャから考えて実装までやることが多かったです。クライアントとしてはHoloLensアプリ開発に携わっており、2017年に得た蓄積を2018年発展させることになりました。サーバーサイド/インフラは、Python/C#/Docker/Serverlessが主になり、一方でSwift/TypeScript を触る機会がかなり減りました。クロスプラットフォームで動かすことが多いため、Golang を触る機会が明らかに増えたのも目立ちました。転職、起業が入って想定よりも少し色々やることになりました。

総じて、自分の経験を活かしつつ足りない部分を学びつつ適用していくことの連続でした。今までの延長のようで、実際はOS、クラウドともに環境を選ばなくなっており一部に安住も固定もできなくなったという意味で、自分の未熟さと向上が求められる一年でした。

退職と転職と起業

グラニ社は2018年退職しました。ちょうど5年いたことになります。入社当初から「3年経ったら辞める or @neuecc がいなくなる時は私もやめる」と伝えていたのですが、2年延長するぐらいには好きな会社でした。延長を振り返ってみます。

1回目の延長は3年経った一昨年で、「黒騎士と白の魔王」のリリースを見届けたいこと + ゼロからVR部門の立ち上げに誘われたことからあまり機会ないのと面白そうと思って伸ばしました。

2回目の延長は昨年で、部門立ち上げてアプリもいくつかリリースしたころで、「黒騎士と白の魔王」のリリース間近だった + クローズドβで問題が確認できないことから社内のモニタリング基盤を一新することになりリリースを成功させるために延長しました。

このモニタリングの変更によって開発から運用まで根底から取り組み変えることになったため、安定するまで見届けたいという思いがありました。このあたりはグラニの開発者ブログで書きました。

engineering.grani.jp

2018年は延長する理由がないので1月にいくつかお会社に訪問して、面白そうなプロジェクトに参画しようと考えていましたが身内の緊急事態があり断念することになりました。*1と思いきや3月に@neuecc が退任することになったので予定通り退職しました。グラニは、neuecc に誘われたから入りneueccがいるからいたという意味で、人に惹きつけられて働くという試みでしたが、想定以上に楽しく自分を鍛える場となりました、ひとえに多くの同僚と周りの人に支えられた結果だったので心から感謝しています。やりたいことと人と仕事が一致していたという意味でとても面白かったです。

3月いっぱいでグラニを退職することにしたものの、どこに行くか決めておらずいくつか会社を訪問し、4月に某社に転職し2週間で退職しています。人生で一番早かったのですが、入社までにかかわった方には申し訳ない限りです。入社や退職エントリーを書いていないことからも詳しい理由は書きませんが、あくまでも自分の想定との乖離が激しかったためであって、その会社やメンバーに起因するものではないです。詳しく聞きたい方はご飯に行きましょう。

で、5月に起業しました。2018年4月時点では後2年先になる想定だったのですが、2年で得るもの、やることを考えた末でした。*2多くの人に支えられてまずはスタートを切っています。知らないことばかりで頭が回らない、日々いっぱいいっぱい感がありますが、本当に周りの人に助けられています。とはいえ年末の2週間は、手痛いドジを2回踏み、日常生活すらも余裕を失い、体力も限界に近かったので2019年はこれぐらいは余裕でクリアできるはず。*3

さて、起業したものの2018年は一人会社なので個人事業主とあまり変わりません。*42018年は、知人の紹介や打診で複数のお会社でお手伝いをしています。

プログラミング

2018年は、前半と後半でやっていることが全く変わっています。 前半は、ASP.NET MVC から ASP.NET Core 2.0/Terraformがメイン、後半はお手伝い先に合わせていろいろでした。

最も使ったのは、C# ですが Golangを読む機会が大きく増えたように思います。

git コミットは、まぁこんなものでしょうか。PowerShell本のコミットが混じっていてちょっとノイズがはげしいです。

CSharp

お仕事としては、ASP.NET MVC の ASP.NET Core MVC 化が大きなトピックでしたがやり残しになったのが残念でした。 一方で、HoloLens + Unity でのお仕事があり、CI/CDの構築からサーバーサイド連携、MVP4U アーキテクチャなどチャレンジができたのは良かったです。

特にMVP4Uは、Zenject による DIをアーキテクチャのために使う失敗に対するカウンターになったのが大きく、DIに対する自分の中の一喜一憂が大きく浮き沈みしました。ASP.NET CoreのDIは、フレームワークが組み込んでしまっているので、どうこう考えるより先に使えばいいでもその場は構わないと思いますが、Unity のDIは自分で意図をもって用いることになり、ここで失敗をしたのは良かったです。現状Unity でDIは、テストかScriptableObjectの解決以外は使いたくないし避けていくと思います。

C# はUnityを除き .NET Core に集約されました。.NET Framework ありがとう、さようなら、でいいと思います。ぐっばい。とはいえ、macOS、Linux 使っていますが、.NET Coreなアプリを動かすことないんですよね、たいがいコンテナ上で動かしてサービス組むので、.NET Core というより、コンテナで動かせる *5という見方です。コンテナとして考えて、開発はC# でできるのはCI/CD、オーケストレーションの全方位でメリットなのでようやく追いついてきた感じがします。

面白かったのがUWPアプリを書いていたことです。HoloLens のごにょごにょで必要になり書いたのですが、昔からXAMLは苦手な部類にあってようやく書ききりました。書きやすいは書きやすいのですが、これを今後も続けるのはどうにもと感じるのでなんともです。UWPも制約の高さが結構あからさまに出てるのが微妙で悩ましいです。今後のUWP全然光が見えないのですが、本当にこれやるんですかね?

Serverless に関しては、Azure Functions v2.0 がGAしたことで、いくつか書き換えが入り ASP.NET Core っぽくなりました。暗黙的なお約束が増えたのがDIに似た気持ち悪さがあるのですが、フレームワークの成熟ってこういう感じかなぁとも感じるので、シカタナイ気もします。

1つ感じるのは、.NET Standard はかなりきつく、.NET Core 最高感です。特に .NET Core 2.2 から楽になった感じが大きいので、今後は .NET Core 2.2 /3.0 がスタンダードでいきたいところです。

相変わらずC# に感じるビルドが苦しいという状況は光明が見えないです。dotnet (msbuild) でのラップはいいのですが、制御しにくさがなかなか大きいです。

シングルバイナリくるくる詐欺が本当にくるっぽいので、期待しています。シングルバイナリ何がうれしいって、CI/CD/コンテナ でめちゃめちゃうれしいのです。

と書いていたところで、反応もらいました。

運用面はどうとでもなるんだけど、それはugly であることを受け入れた結果なので、ベストではないと考えています。シングルバイナリならコピーと参照がシンプルになるのは明らかです。 ファイルを指定するか、フォルダを指定するかの違いは思ったよりも大きくて、基本的に対象が単独ファイルであることは簡単です。とくにフォルダになった場合、子フォルダも考慮する必要が出てくることがほとんどなので、シングルバイナリの楽さに比べて格段にめんどくさいと常々感じます。シングルバイナリでないだけで、余計な手間とコストがかかっているので「2019年にもなってそんなこといい加減なんとかしたい」というのが気持ちの根底にあります。ただ、dll差し替えで済むケースなど部分的な入れ替えとか考えだすとアカンコレなのでバランスなのでしょう。

Python

お手伝いの流れで、メイン言語で読み書きをしていたという意味で触る機会が増えました。C#以外は全てVSCode なのですが、コードレンズで追っかけるのが楽なので始まりのエディタとしては良さがありました。もともと時々読み書きすることはあったのですが、VSCodeで案外楽になっていたのは良かったです。

某SDKを使っていたのですが、エラーから原因を追いかける時にスタックとレース通りではあるもののエラーメッセージから状況が読めず、周りからも同じ見解であるものの解消まで手間取ったというのがありました。デプロイしないと状況が発生しないこともあり、リモートデバッグどうすればいいんだ、となりながら苦しかったので対処知りたい。

pyenv 苦しいので、全面的にpipenv 使えばいいと思います。あとDockerではpyenv とかせず、おとなしくpython をいれましょう。その方がパスと依存性がきっちり制御できて時間短縮できます。pyenv やばい、pyenv 使いたくなくなった。

そういえば、Azure / AWS / GCP全て Python 3.x で問題なくなっているので、Python 2.x は使わなくなりました。

Golang

Windows/macOS/Linux 全ての環境を触ると出てくるのが、クロスプラットフォームで上手く使えるツールなのですが、Golang の独壇場でした。それもあり、OSSツールを使うことがほとんどなので、Golang を読む機会がかなり増えました。

一方で、余り書く機会を作れずにいたので、Golang書く機会を強制的に設けるようにしはじめました。どうしても必須なので、しばらく維持して書く機会を強制的に増やす方向にしています。

PowerShell

これまでと違ったこととして、2017年末に出版社様から打診をいただいたPowerShell本の執筆が決まり書き上げました。

tech.guitarrapc.com

反省も書いたので、次があれば生かすということで。

tech.guitarrapc.com

PowerShell 6.0 がリリースされ、6.1 がリリースされたことでPowerShellも .NET Core に踏み出しました。*6

PowerShell Coreについては本でも書きましたが、参考になるモジュールを用意していなかったので書いたのがUtf8BomHeader です。

tech.guitarrapc.com

私自身のPowerShell を仕事で用いる頻度は、Windows 100% なのですが、個人では60% Docker (Linux) / 40% Windows なのでまぁこんなものかもしれません。そもそもWindows以外の仕事でPowerShell を用いる理由がないのですが、Windows、特にAzureだと用いているのが楽なケースが散見されるのでまぁそんなものかと思います。

いろんなお会社でいろんなやり方を見ている限り、PowerShell Core はWindowsがない環境で標準的に使われるのはないと感じますがもう少し様子見で。Azure は PowerShell でしかできない操作があるの本当に良くないと思います。

ShellScript

PowerShell よりも圧倒的に書くことが多かったのは当然Dockerで使うからです。Terraform と組み合わせることが多く、このやり方はつらいことへの一直線なので悲しいものがあります。

ある意味では素直なので、このままちょくちょく書くことが多い状況は続くのでしょう。

インフラ

お手伝いで求められ、発揮しやすいのがインフラとアプリをつなぐ部分とサービス全体の安定化です。ということで、インフラ的な部分は2018年もやってます。

特に、Docker、 Terraform + Ansibleが多く、Kubernetes に少し踏み込んだ感じでした。

Terraform

一年通して、Terraform でした。

Azure/AWS/GCP どのプラットフォームも書いていましたが、どこも癖が違って微妙にやりにくいのが解消せず厳しい..... とりあえず、AzureRm Providerに関しては、10以上Issueあげたりしたので、2018年末はだいぶん楽になりました。2018年5月はどうしようかと思いましたが、なんとかなるぐらいにはなってよかった。

AWS がやはり一番書きやすいというかリソースがそろっている感じはあります。書きやすいというと嘘で、Regionが露骨に出てくるので、どうstateを分割するのかは悩ましいところがありますが、Organization含めて、アカウント分離はしやすく安定して模範的な環境だと思います。2018年は、年始と年末でだいぶんTerraform への取り組みや構成、CI、Atomic担保が構成、説明できるようになったので次に行きましょうという感じです。

GCP でも問題なく書くだけのリソースはあります。マルチクラウドがふつうなので、Terrafornを使うのは妥当な判断だと思いますが、もう少しいい手がないかは常に悩むものがあります。2019年はそこかしら。

なお、カクカク詐欺な記事を書いているのでちょっと待ってください。(いつリリースなの)

Ansible

Ansbile + Terraform + Packer はまぁ安定なのですが、あんまり好みではないのでいやですね。ここはもう少しなんとかならないかなぁ感とお付き合いします。

コンテナ

コンテナに関しては、普通に開発フローに組むのが楽で当然あってしかるべきな状況が一段と浸透しています。むしろコンテナを前提にしないとCI/CDの難易度が高いという方が適切かもしれません。

そういう意味では、.NET Core + k8s は良い流れで、来年はこれが普通かなぁと感じます。ローカル開発バックエンドならDocker-Compose でいいのですが。

AWS

2018年は、嫌い/苦手だったサービスに真正面から取り組んだのでちょっとは楽になりました。

Organizations をベースに、マルチアカウントも楽になっているので引き続き楽になっていくでしょうし期待です。

sts の改善や2018年re:Invent でAWSはいい感じにゆるさと硬さのバランスが取れてきたのはあって、とてもバランスが良くなったように感じます。

Azure

2018年最も触ったクラウドはAzureになりました。

Portal を除き、Terraformのみで扱える範囲に関してはずいぶんと楽になりました。一方でリソースの生成、削除の時間のかかりよう、Atomic性が壊れる当りの挙動が一年通して安定しないのでそんなものかなぁと割り切っています。Preview だから、というのが出てきて一向にGAにならなかったりするのは苦しいものがあります。

MSI が使いやすいので、MSIを標準的に使いたいところです。

初期はいいけどサービススケール時以降は使いたくはない感じがありどうしようか悩ましいです。

AWSやGCP含めて使ってて感じるのは自分のサービスの最大の顧客が自分なんだなという感じに対して、「Azure の最大の顧客ってもしかしてMicrosoft じゃないのでは?」という感じがあり、Azure の今後に注目しています。

GCP

会社のメインクラウドはGCPです。理由はいくつかありますが、手間とスケールと安定のバランスから行くとちょうどいいというのがあります。

AWSだと手間が大きい、Azureは安定とバランスが好みではない、ちょうど自分がほしいのに近いのが現在はGCPです。

ヘビーに使うので、しばらくは注力が続くでしょう。

コミュニティ

2018年は、try! Swift や CEDEC に初めて参加しました。両方とても刺激的で面白かったです。会社のふるまいが見えるので、結構おススメです。

Yamagoya Meetup 2019やGoogle INSIDE、Serverless Conf 2018 も参加しましたが、最高でした。また参加したいぐらいには。

Unity 完全に理解したシリーズにも出てましたが、どれも学びがありました。「完全に理解した」は語句としては意味と内容がずれてるのでもう少しいい表現ないのかしら。

コミュニティに人が多いと私はすぐに逃げてしまうので、コミュニティ後の食事とかで見かけたらレアです。必要がない限り、今後も逃げると思います。

2018年は、書いているセッションノートを即公開することを試みてみました。Kibela にセッションノートを書いているのですが、これをそのままはてなブログに張り付ける方式です。ここ数年文章構造を作りながら書くようにしているので余り困らない感じがあります。

記事

21本、すくないです。PowerShellが多いのはいいですが、あんまりよくないです。お手伝い先で週2-4本書いているのでそこで体力使っている感じがあります。2019年は、公開前提で書けるといいかもなので調整してみます。

ライフスタイル

睡眠時間が年間平均3時間10分だったので、もう少し寝るようにします。また、ランチを抜くとかなり快適に一日を過ごせるのが分かっており、朝夜ご飯を徹底するのがよさそうです。昼食べる場合は、炭水化物を抜けばよさそうです。

PCの利用状況、睡眠リズム、徒歩数を計測することで、ある程度自分の傾向と反省を週一で促せるのでキープするとよさそうと感じます。また、ストレッチをしていると体が楽なので、これも継続がよさそうです。

さて、自分の欠点である「個人に依存することはエンジンがかかるのが遅い」を解消するために以下の対策をうちました。

  • 消費時間を計測する
  • Youtube をウェブサイトブロッカー (Beta) でブロックする
  • 期限を設ける
  • 一人でもセルフレビューをいれる

エンジンがかかるのが遅いのは他のことに時間を費やすことから見えるはずなので、何にどれだけ時間をかけているかを計測することを始めました。 私は、Windows / macOS両方を使うので、Rescue Time を使って一日のざっくりとした時間消費を測っています。

www.rescuetime.com

私は普段 Chrome を使っているのですが、自宅の環境でウェブサイトブロッカー (Beta) を使うことで、Youtube を完全にブロックしました。iframeは見れるので、何かの記事のYoutube閲覧には困りません。

期限に関しては、Google Calendar でこまめにスケジュールに組むことを徹底することで、上手くいっています。

セルフレビューは、Geekbot を用いて自己報告をしています。感じるのは、「年配の経験つまれた方に一日1回レビュー受けるサービス」受けたいなぁと。*7

2019年は?

会社としては、2019年は作りたいサービスを形にして何本かリリースするため働き方をシフトします。*8 個人は、会社のサービスリリースと収益化です。注力する言語的にはC# / Golang 、ツールはTerraform + Kubernetes + GCP/AWS/Azure + Unityです。

2019年は2018年より、さらに(自他に)求められるレベルが高くなっており、天井知らずを感じつつ楽しみたいと思います。

あとは自分の習慣の改善なので、ガンバリマス。

*1:予定になく先方にも申し訳なかったです

*2:予定では7年前に起業だったので予定より遅れた

*3:あるいは働き方を変える

*4:事業計画通り

*5:Windows Containerではない

*6:リリースして初めて踏み出せる

*7:なんていうんでしたっけ、すごいいいなぁと思ったけど忘れました

*8:事業計画通り

PowerShell 本を出版するまでの反省

PowerShell本を書いたのですが、当然多くの反省があります。

tech.guitarrapc.com

どれも自分の苦手とすることへの直視を求められるのでメモしておきます。

プログラミング系の本を書くときの参考になれば幸いです。

目次

そもそもなぜ本を書いたの

本を書く動機はいろいろなケースがあると思います。

私の場合、本を書きたいというより「本を通してPowerShell に感じる悩ましいと思わせる部分への一定の解消を図りたい」という思いで書きました。 結果は読者のみぞ知るので分かりません。

もともと私が持っていたPowerShellの課題に「学習コストが高すぎる」というのがあります。ただコマンドを実行するだけならいいのですが、適当に書いたスクリプトがよくわからないけど動かない、意図した結果にならないというのは、今でもそこかしこで見ます、聞きます。「PowerShellは学の大変だから触るのすら忌避する」という声をよく耳にするたびに、ナルホドタシカニと感じてきました。

自身、今まであったPowerShellの本で自分の知りたいことが知れず、手さぐりで学んできた部分が大きいです。PowerShell InActionは良い本ですが、この本でPowerShellに感じる苦しみは解消されませんでした。配列やスコープは理解できず、モジュールも何それです。英語出版本も10冊近く持っていますが、どれもスコープは小さく、知っている前提、レール上で使う前提でした。そのため、自分がやりたいことをやるためにPowerShell Utils などの小ネタ置き場を作ったり、モジュールを公開し、シェル芸を書き、一つひとつ学んだことを記事にしたり、MSDNやDocs が更新されると読んで新しいことを学び、誤りに気付き、理解のすり合わせを行う繰り返しをしています。

この状況は、ブログを書いていても「調べることができる人」という条件がつくためリーチ層が限定されます。そこで、編集者さんからお話をいただいた時に、リーチできない層に対して、次のニーズを満たすものを伝えるものがないなら書こう、それを書けせてもらえるなら書くというのがこの本の根幹です。

  • 学ぶコストを減らすため一冊で全然書いたことがない人、書いたことあるけどただコピーしただけ、ちょっと書いてる、普段から書いてる、めっちゃ書いているをある程度網羅する *1
  • とりあえずこれ読めば罠とか理解して回避方法もわかる
  • どう書けばいいかのサンプルが分からない状況にサンプルを提供する
  • 後でわからない時にでも読みなおせる
  • 途中まで読んで、分かるようになったときに先も行ける、

執筆期間

本どれぐらいで書いたのか度々聞かれます。だいたい2カ月です。

内訳は、1-2月の深夜、3月半月 + 4月半月 + 5月半月。 3月以降は仕事がないので一日12-18時間ぐらい当ててたようです。*2

仕事をしながら書ける人、心底尊敬します。気持ちの切り替えがポイントなのですが、難しいと感じました。

反省点

いずれも自分への甘えと見込みの甘さが露呈することになります。

書き始めるまでの重さ

最も反省するべきは、エンジンがかかるまでが遅いという悪い癖が露骨に出て足を引っ張りました。

本は書きたい、締め切りもあり書かなきゃなのですが、どうしてもVS Codeを開くまでの気持ちを持っていくのがムズカシイ時期がありました。 仕事をしている時の深夜、締め切りを意識していない時、意識し始めた時それぞれでまんべんなくあるので、私の欠点です。

対策

  • 時間を消費していたことをしようとしたときに閉じる (主に読書とYoutube が大きかったように思います)
  • 音楽を決める

もともとYoutubeでバックグラウンドミュージックを書けて、プログラムや記事を書く習慣があります。が、この時期に動画の音楽を流すことをしてたせいで動画を見る、映像に注目する悪い癖がつきました。そのため、Youtubeで一日の時間が溶けることもあり、開くのをやめました。小説は気分転換がずるずると、パターンだったのでそもそも開かないことにしました。

音楽は意識を集中させるために使っているので、実はなくても書けるのが分かっています。事実、音楽が途中で切れてても一日中書いていることも多いです。しかし音楽を使って意識を書く方に集中させる、書くことの環境であると自己暗示書けることに使っているためある方がスムーズに書き出します。そこで、Playlistを決めて、エンドレスに流すことで、音楽と執筆を結びつけました。主に "Put Your Hearts Up"が多かったです。

一日の書ける量

色々な方が書かれていますが「ブログと全然違って全然書けない」です。「適当を書けない」という当然の事実がびっくりするぐらい書けなくしました。まさか一日10P程度も書けないことがあるとは思いませんでした。*3結果、締め切りを延長したにも関わらず直前まで修正していたので、本当に余裕ありませんでした。

文章構造も書きにくさの原因でした。私が読書をしていて一番嫌いなのが、事前に知らないことを先に出して後から説明されることです。*4知らない概念を先に出した場合、思考のジャンプが生じるためかなり嫌っています。今回、自分で書いていて発生したため、文章構造を5回書き換えています。始めに目次とプロットを書いていても発生したので、レビュー時に考えることにしてある程度割り切りました。*5

私は、RE:View を使っていたのですが、初めてのRE:Viewによる書きにくさは一週間程度ありました。その後もプレーンテキスト書くよりもRE:View書式当てることの手間はありますが書ける量はそこに起因していないように思います。

対策

  • プロットを細かく出す
  • 今日はここまでという目標スケジュールを紙に大きく書いて共有しました
  • 文章構造をブロックごと入れ替え、つなぎはレビューまで割り切る
  • 事実に基づいて淡々と書いてから肉付けをする

プロットと一日に書けるページ数(10P-50P)をある程度同期させることでスケジュール管理しました。同期が崩れると進捗が不明で完了が未定になるので、これはマストな約束事として自分の中で持てました。

目標の達成度を見える化することで、プレッシャーを外部から与えてもらいました。案の定レビュー時に入り乱れて機能しなくなったのですが、文章を書いている分には、自分のダメ具合が露呈するのでオススメです。

「文章構造を考えながら書く」のと「ブロックごとのテーマについて書く」では、難易度がけた違いです。ブロックごとのテーマに集中するのは非常に簡単で、ブログが書きやすい理由はこのテーマが決まっていることにあります。本を書くときに「全体の構成を考えながら書く」のは、章ごとに分離しても難易度が高いため辞めました。ブロックごとに書くようにしたことでかなり書きやすくなりました。

文章に肉付けされていると前後の文脈が作られてしまい、ブロックごとの移動をする修正が困難です。淡々とした文章で書いておいて、文脈を整えるようにすると移動が楽になりました。文章を書く基本なのでしょうが、なかなか気づけず無駄に時間を使いました。

表現の難しさ

網羅するというのが本当に厳しく、文言の表現でも悩みがずっと解消できなかった部分です。そのため、文章に日本人じゃないんじゃないか、というレビューはそうですねと感じます。誤字脱字の多さもレビュー時点から指摘を受けています。編集者さんの修正が結構入っているのですが、それでも誤字脱字あるので元がまずいのは明らかです。

対策

  • もっとすっきりした文体にする

最終版でも、文体がくどいためすっきりしていいと感じます。

ページの超過

当初、編集者さんと合意した目標ページは400Pです。蓋を開けると624Pでした。まさかの224P超過、いいわけもありません。Goを出してくださった編集者さんにはお礼しかありません。

対策

  • プロットをきっちりねる

中盤にプロットを練りなおした時に誤差が小さくなったことからも、当初の見込みの甘さは明らかです。 この影響は多きく、最も面白いであろう5章がページ相対量が少なくなっているのは完全な失敗でした。

サンプルコードの担保

自分が読んでてほしいので、サンプルコードをふんだんに入れましたが、その動作確認をWindows PowerShell / PowerShell Core (Windows / macOS / Linux) の書く環境で行うことが思った以上にコストになりました。

対策は次の通りです。

  • サンプルコードに依存する文章だけコードの変更がないように気を配り、他はコード修正を前提にする

本質的には、マトリックスでクロスプラットフォームテストをかけるのがいいのですが、執筆中にテストを用意するのが過負担でした。誰かにお願いすればよかったです。

兼業はできない

1-3月を通して日中仕事している時に夜書くのは私にはムリでした。1章は深夜1カ月 + 3月の数日を当ててようやく書きましたが、非常に苦しかったことを覚えています。

また、退職と無職期間がまるまる執筆で費やされたのでのびのびできなかったのが悔やまれます。2018年の最優先課題だったのでシカタナイのですが、無駄に溶かした時間を考えると反省しかないです。

編集者との文章共有

GitHub + RE:View で書いていたのですが、編集者さんの印刷用割り付けとGitHub が連動できなかったことが悔やまれます。 編集者さん側の文章をGitHubにわざわざ反映してもらう必要があり、2度手間どころではありませんでした。 RE:View を用いたにも関わらず、RE:View で書籍製本もできなかったので、いろんな意味で悔いが残ります。

対策

  • 編集者さんに合わせる重要性を理解してもらう
  • 編集者さん側の文書フォーマットに合わせるなり、完全に同一フォーマットを用いる
  • Diff を活用する

なに使っていますか? これでいいですか? というすり合わせをはじめにやっていますが、こちらの自由でいいといわれた時に、この問題を予見できなかったのは自分の未熟を感じます。

手間をなくすためには、同一フォーマットを使うのが最善です。これはWeb寄稿ではなかった課題で、結構難しいと感じました。GitHub + RE:View or Markdown が標準になると、私は嬉しいですがどうなんでしょうか。

Diff を活用は実はしていたのですが、フォーマットが同じでないので漏れをお互いにシステム的になくすことができなかったのはムリゲー感じます。

重版予定は?

今はありません。本が分厚く余り出ない一方で、電子書籍が良く売れているということで鈍器なんでしょう。

もし重版が出るなら誤字脱字や表現上の修正、PowerShell Core 6.1/6.2の対応をしたいのですが、予定は未定です。

*1:この時点で欲張り

*2:ぜんぜん遊べておらず仕事よりハードでした。

*3:ブログの文章換算で甘く見てました

*4:言葉を置き換えて先に出すのはいい

*5:案の定レビューで指摘を受け、粛々と直しました