fossil-scmの th1で httpアクセス

個人的に文章書いたりソースコード書いたりといった時に fossil-scm を長年使っています。

特徴としてはこんな感じ。

- Distributed / Centralized どっちも行ける感
- wiki / Bug Tracking等を内蔵
- 1ファイル／コマンドで全部入り（コピーすればすぐ使える）
- repositoryも1ファイルでバックアップ楽ちん

コマンド一発で 各種 Server としても動かせてとても便利なのですが、内蔵している th1スクリプトからの httpアクセス方法については、ドキュメントや実装上の問題等も多いので Webで検索しても成功例がなかなか見つからないと思います。

色々制約はあるのですが、通知目的ぐらいであれば全然出来ないってわけでも無いのでそのやり方について。

なお、CommitやPush時等で Chatbotを実装したいといった場合は Hookを使う方が楽ちんなはず。

## th1スクリプトについて

th1は Tclを元にしたミニマルな言語です。

th1のスクリプトはコマンドの羅列。ifも 一つのコマンドです。

コマンドは 改行か ; で終端。

{ } で括られたのはブロックではなく複数行文字列。

言語としての文法らしい文法があまりないのでシンプルですが、Cっぽく見えちゃうあたりが逆に罠な感じに思います。
「なんとなく見ればわかる」気もしますが、一度公式のドキュメント等を参照していただくと良いと思います。

ベースにある tclについては tcl.tkのサイト にある[ドキュメント](http://www.tcl.tk/doc/)。
日本語の解説だと[株式会社フリーソフトネット](https://freesoftnet.co.jp/)のサイトにある[解説](https://freesoftnet.co.jp/webfiles/tclkits/doc/tcl.html)が、とても役に立つかと思います。

th1 でサポートしている tclのコマンド及び、th1 独自コマンドは [fossil-th1 公式ドキュメント](https://fossil-scm.org/home/doc/trunk/www/th1.md) を参照してください。

### th1: httpコマンド

[httpコマンド](https://fossil-scm.org/home/doc/trunk/www/th1.md#http)は以下の2パターンだけになります。

- GETなら `http -asynchronous -- URL`
- POSTなら `http -asynchronous -- URL POSTDATA`

公式ドキュメントを見ると POST 時の Content-Typeは text/plain だと書かれているのですが、
正しくは application/x-www-form-urlencoded かと思います(2.15.1時点[^trap1]) 。

この呼び出しの結果を受け取る方法は現在のところ無い。はず。

### th1-uri-regexp 設定

th1 からhttpアクセス可能な URLはなんでもOKというわけではなく、
各レポジトリ/グローバル設定の th1-uri-regexp に 正規表現で許可するURLを設定する必要があります[^trap2]

この設定は repository毎の local設定で良いと思うので **repository を checkoutした フォルダに行ってから**、以下のようなコマンドを実行します[^trap3]。

例では DiscordのWebHooks APIに限定して許可するような設定です。

```bash
fossil status && fossil set th1-uri-regexp "https://discord.com/api/webhooks/.*"
```

個人的に使ってるだけなら globalで全部許しちゃうというのもありかと思います。

```bash
fossil set th1-uri-regexp "http.*" --global
```

## 認証局証明書の設定

fossilは ssl通信に opensslを使っています。
信頼出来るルート証明書について OSが持っている証明書ストアを使わないため、なんとかする必要があります。

Debianや Ubuntu等では `sudo apt-get install ca-certificates` で行けると思います。
macではやったことないですが(作業不要かな？)、Windowsの場合は以下の二通りのうちどちらかで対応します。

### Windows対応その１ ルートCA証明書をインストール

ここは色々問題がある[^trap4]のですが、公式が配布している x64版 fossil.exe の場合、
`C:\Program Files\Common Files\SSL\cert.pem`に証明書を置くという方法が取れます。

設定する証明書は、PublicなCAでれば curlが配布している [ルートCA一覧](https://curl.se/docs/caextract.html)を使うと良いでしょう。
ダウンロードした `cacert.pem` を `C:\Program Files\Common Files\SSL\cert.pem` として保存します。

curlが使える環境であれば 管理者権限で以下のバッチで設定できます。

```batch
mkdir "C:\Program Files\Common Files\SSL"
curl -LRs https://curl.se/ca/cacert.pem -o "C:\Program Files\Common Files\SSL\cert.pem"
```

管理者権限が無く、上記のパスにファイルを置けないという場合、
以下の「その２」の設定を試してみてください。

### Windows対応その２ ルートCA証明書のパスを指定する

[ssl-ca-location](https://fossil-scm.org/home/help?cmd=ssl-ca-location) にフルパスで設定を入れれば そのファイルが使われます。

サンプルの設定バッチファイルとしては以下です。

```batch
SET "CERTPATH=%LOCALAPPDATA%\fossil_cacert.pem"
curl -LRs https://curl.se/ca/cacert.pem -o "%CERTPATH%"
fossil settings ssl-ca-location "%CERTPATH%" --global
```

ここでは global設定としていますが、Private CAを利用している等で、CAの証明書を設定したい場合は Repository Localの設定を入れる等すれば良いかと。

## 設定確認

テストとして以下のコマンドを実行します。

```bash
fossil test-th-eval --open-config "http -asynchronous -- https://discord.com/api/webhooks"
```

**何も出なければ** 設定はうまくいってると思います。

th1-uri-regexpの設定にしくってる場合は以下のように `url not allowed` エラーが表示されます。

```console
C:\work\fossil\project>fossil test-th-eval --open-config "http -asynchronous -- https://discord.com/api/webhooks"
TH_ERROR: url not allowed
```

CA証明書の設定が出来ていないと以下の様に Verify出来ないのでサーバ証明書を受け入れるかどうかを聞いてきます。

```console
C:\work\fossil\project>fossil test-th-eval --open-config "http -asynchronous -- https://discord.com/api/webhooks"
Unable to verify SSL cert from discord.com
  subject: C = US, ST = CA, L = San Francisco, O = "Cloudflare, Inc.", CN = sni.cloudflaressl.com
  issuer:  C = US, O = "Cloudflare, Inc.", CN = Cloudflare Inc ECC CA-3
  sha256:  8a50f2ecf6273f9416179361a05abe5fe2c6b9acb227efee8eebc511f902972a
accept this cert and continue (y/N)? N
TH_ERROR: SSL cert declined
```

なお、ここで受け入れをして y を答えるとSSL証明書確認の例外として保存する事も出来ます。

```console
accept this cert and continue (y/N)? y
remember this exception (y/N)? y
```

この受け入れ情報が保存されるのは repository localではなく、global設定("%LOCALAPPDATA%\\_fossil") のDBなので、ここらへん理解した上で実行する必要があるかと思います。

また、fossil uiで開くWeb UIでも th1スクリプトを入力する事が出来る(http://localhost:8080/admin_th1 等)のですが、設定をしくじっていた場合、Webページが戻ってこない事がある[^trap5]のでコマンドラインでやりましょう。

### Discordへの Chat送信

試しに Discordの Webhooksに Postしてみます。

まずth1スクリプトファイルを作ります。WEBHOOK.ID と WEBHOOK.TOKEN は自分で取得して設定してください。

```th1:discord-post.th
http -asynchronous -- {https://discord.com/api/webhooks/WEBHOOK.ID/WEBHOOK.TOKEN} {content=Hello%20from%20Fossil-SCM}
```

コマンドで投入

```batch
fossil test-th-source --open-config discord-post.th
```

こんな感じで postできれば 確認完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/941141c6-8528-630b-3466-6776a3369749.png)

## まとめ

- 認証局の証明書の設定を忘れずに。特にWindows
- デフォルトではhttpアクセスはすべてのURLについて禁止されているので、許可するURLを設定
- POSTする Content-Typeは application/x-www-form-urlencoded

[^trap1]: [it will be interpreted as text/plain and the POST method will be used](https://fossil-scm.org/home/doc/trunk/www/th1.md#http)
[^trap2]: これくらいは[httpコマンドのドキュメント](https://fossil-scm.org/home/doc/trunk/www/th1.md#http)に書いておいても良いと思うのですが……。[設定値](https://fossil-scm.org/home/help?cmd=th1-uri-regexp)のドキュメントには記載があります。
[^trap3]: Fossilの管理下にないFolderで実行すると Global設定になってしまいます。fossil statusを仕込んでいるのはfoolproofです。
[^trap4]: [X509_STORE_set_default_paths](https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_set_default_paths.html)で設定しているので、hardcoded default paths で設定されます。opensslが dllリンクだと環境依存のため不定としか言えない気がしますが、公式のfossil.exeは static linkのため、2.15.1 でのデフォルトは この記事に書いたパスです。`C:\Program Files\Common Files\SSL\certs\*` のディレクトリ指定もあるはずです。ただ、私が試す限りこちらは効いてないようでした。原因までは探っていません。
[^trap5]: 多分SSL証明書のConfirmationで止まってるような気がしています……。