更新日:
投稿日:

私は2020年の末あたりからLet's EncryptのDNS-01 challengeをValue-Domainで達成するためのスクリプトであるvalue-domain-dns-cert-register (vddcr)の開発を行ってきていたのだが、メンテが辛くなってきたので、今回Shellscript(Bash)Perlでvalue-domain-dns-utilとして作り直した。その経緯と、開発中の学び、そして最後にどうやって作っていったかについて書いていく。

背景

一年半ほど前からTypeScriptに限界を感じてきたことや、当時の誤った単体テストへの偏執により、vddcrはメンテしたくない状態になってしまっていた。

そこで昨夏、つまり2025年の8月に式年遷宮、つまり全体的な書き直しを行ったのだが、個々の処理をする部品を作ったまではいいものの、それらを繋ぎ合わせるところで飽きてしまい、放置していた。vddcrはこの時点で事実上の放棄状態だった。

しかし、自宅サーバーの運用をしていく中で別の目的でValue-DomainのDNS APIと関わらなければならない必要が出てきたため、今回、vddcrの置き換えも兼ねて新しく作り直すことにしたのだ。

具体的には複数ドメインに対するDDNSをバルクでやりたい要求が生まれたが、Value-DomainにあるDDNS APIは一度に1ドメインしか処理できない上にレートリミットが60秒に1回と極めてキツく、複数ドメインに対して行うには余りにも遅延が大きかった。

例えば10ドメイン更新しようとすると10分もかかるわけで、これでは使い物にならないというので、DNS APIを直に叩いて一気に書き換える必要性が出てきたのだ。どうせ作るならvddcrが担っていたDNS challengeの機能も包含できる形にしたかったので、Value-DomainのDNS APIを便利に叩くためのツールキットとしてvalue-domain-dns-utilを作ることにした。

つまりvddcrの純粋な後継ツールというよりは、vddcrの役目もこなせるツールキットを開発し、vddcrと同等の機能を有するツールを実装サンプルとして同梱したわけだ。

Shellscriptにした理由

ものの数時間でPerlに直したので過去の話となった。移植性の関係でbashはイマイチだった。

ぶっちゃけそんなに大した理由はなくて、単に目の前にターミナルとテキストエディタがあってパッと組めたからという部分が大きい。実際ベースはパッと作れた。まぁ作って半年も放置していたのが…。ただまぁPerlの方が保守性は高いだろうし、移植性もあるだろうとは思うから、気が向いたら書き直すかもしれない。

ちなみに別言語に移植することの検討はvddcrでも行っていたが、最終的には候補になかったShellscript(Bash)での実装に落ち着いた。そもそもこの課題の存在を忘れていたので、検討すらしていなかったのである。

value-domain-dns-utilについて

今回作ったvalue-domain-dns-utilは、vddcrの単純な代替ではなく、Value-DomainのDNS APIを叩くのに便利なShellscriptの関数群だ。DNS challenge以外にも用が生まれたので、このような汎用的な形になっている。

DNS APIから現在のレコードを取得する関数や、取得したレコードから先頭一致のパターンマッチでレコードを取得する関数、レコードリストへの追加や置換をする関数、そして編集したレコードをDNS APIに送り戻す関数を備えており、DNSに関する一連の編集処理ができるように便利に作っている。

結果として、Value-DomainのDNS APIを叩く汎用機能があれば、それを流用してLet's EncryptのDNS-01 challengeもできるし、当然複数ドメインに対するDDNSをバルクでやることも可能になる。

学び

開発中に発見も多くあったので記していく。なんかこういうのまとめた記事かページ作りたいね。

echoでドルマーク始まりのシングルクォート文字列を作ると、その中の改行コードが展開される

例えば以下のようになる。これは一行で改行展開を書けて変数に入れられるので、インデントされているコードなどで物理的にへし折りたくないときなどに有用だし、文字列としてのコードが見えるのでわかりやすい部分もあると思う。

入力

#!/bin/bash

# ドルマーク始まりのシングルクォート文字列は改行が展開される
echo $'1:aaa\nbbb'

# つまりこれと同じ
echo "2:ccc
ddd"

# ダブルクォートの中の\nは展開されない
echo "3:eee\nfff"
# ドルマーク始まりのダブルクォートも展開されない
echo $"4:ggg\nhhh"

# 一行で改行展開を書けて変数に入れられるので便利
hoge=$(echo $'5:iii\njjj')
echo "$hoge"

出力

1:aaa
bbb
2:ccc
ddd
3:eee\nfff
4:ggg\nhhh
5:iii
jjj

変数の最後の一文字を削る

これは後方一致除去(${parameter%word})と呼ばれるものらしく、詳しくは調べていないが"hoge"に対してfugaを繋げ、"hogefuga"としたい場合に便利だ。

例えば以下のように書ける。

入力

#!/bin/bash

hoge='"hoge"'
fuga='fuga'
result="${hoge%?}$fuga"'"'

echo $result

出力

"hogefuga"

sedで改行を改行コードに置換(\n\\n

これは見つけた当時sedの記事にも書いているが、以下のようになる。

入力

cat <<'EOF' | sed ':a;N;$!ba;s/\n/\\n/g'
aaa
bbb
ccc
ddd
EOF

出力

aaa\nbbb\nccc\nddd

[]ではGlob展開できないが、[[]]では出来る

今のところこういった形式でGlobを書くことはないのだが、Sonnet 4.6にテストコードを書かせている中で発見したので書き留めておく。

入力

#!/bin/bash

hoge='aaabbbccc'

[ $hoge = *"bbb"* ]
result1=$?
[[ $hoge = *"bbb"* ]]
result2=$?

echo $result1
echo $result2

出力

1
0

あとがき

LLMとの作業分担

今回の開発でもLLMを活用したのでその辺り。

今回の開発では、8ada152~93d58ccのコミットを行った。最も古いコミットである8ada152ではテストコードを除き、私がフルスクラッチで書き、テストコードはClaude Code(Sonnet 4.6)に書かせた。

次の029a951では、私が書いたコードをClaude Code(Sonnet 4.6)に全体的に直してもらって私がレビュー・微修正するという開発形態をとった。それ以降のコミットはコメントなどの微修正なので、再び私が手動で書いている。

こうした分担により、設計の根底にある信条のようなものには私の色が濃く出ているが、実装上の問題点はClaude Codeによって大きく改善されている。特に029a951では、typoや単純な実装ミスの修正に加えて、bashの文字列処理に不慣れなことで生まれたエスケープシーケンスの山が整理された。jqの-rオプションの有無がまちまちだった部分も統一され、可読性がだいぶ向上したと思う。

とはいえ、Claude Codeの言うことを鵜呑みにはしていない。こちらから明示的に指示して直させた箇所もある。例えばテストコードのappend_recordreplace_recordのアサートは元々Globで書かれていたのだが、より厳密にするために全文マッチに直させた。別にレコードの順序がどうなっていようとValue-Domain APIは気にしないどころか勝手にソートされるので実用上の意味はないのだが、こういうのは人としての拘りである。

昨今は過去の記事でもちょいちょい言及している通り、LLMを使った開発をそこそこしていて徐々に馴染めてきているので、その点も含めてよい経験になった。

抽象化した今回の設計と、過去の歪な設計を振り返って

抽象化して再利用性を高めた今回のツールキット方式と、単体テスト大正義とばかりに変な作りになってしまった前回の方式の振り返り。

過去のvddcrでは単体テストへの誤った偏執によりモジュールが具象的な単位で分割され、非常にメンテナンス性が悪く、再利用性もないものになっていたが、今回はちゃんと抽象化して、責務ごとに機能が切れたと思っている。このことによって再利用性が高く、使いやすいものが作れたと考えている。大した規模ではないとはいえ、部品を組み合わせて処理を組むには程よくいい感じだろう。

先ほどのことや、単純に機能を削ったこと、TypeScriptの複雑すぎる型管理や非同期処理から解放されたこともあり、コードもだいぶ見通しがすっきりした。ツールではなく、ツールキットという形で作ったことも奏功し、Value-DomainのDNS API周りのスクリプトは大分作りやすくなったと思う。将来的にバルクのDDNSスクリプトを書くのも楽になるはずだし、重い腰を上げて作れてよかった。

式年遷宮で起きる変遷について

式年遷宮をする以上、元の状態には戻れないという話があり、それが何故起きるのかみたいなところが分ったような、分からないような、そんな話。

しかし世の中には式年遷宮で全く違う姿になるものがある。今回作ったのはまさにそんな感じで、なんというか作者の気持ちがわかった気がする。

分かりづらい例えだが、FF14の外部ツールであるConcept Matrix(CMTool)がAnamnesisに変わったときは、全く別物になった気さえしたし、こういった式年遷宮によって元あったことは一応できるにはできるけど、複雑になったり、なんかかゆいところに手が届かなくなったり…みたいなのは他でもちょいちょいあると思っていて、今回vddcrをvalue-domain-dns-utilにしたのもまさにそうだと思う。

という背景があるのもあり、完全互換を目指したvd-dcr.shを同梱することで、この不便さを少しはなくそうとした。いやまぁ自分のためにやってるので、自分のためのことではあるのだが…。とはいえ、以前のと比べるとプラットフォーム別の互換性は落ちていると思うので、現時点だと動かない環境は増えている気がする。

あとがき2:結局Perlに変えた

動かそうとしてた環境の一つのシェルがashで、bashが使えないことに気が付いたのでSonnet 4.6に頼んでサッとPerlに変えてもらった

関連記事

ググって出てきた記事が軒並み古くて役に立たなかったので、令和八年最新版として書いておく。

やり方

  1. CPANMのインストール
    curl -L https://cpanmin.us | perl - App::cpanminus
    
  2. CPANMのパスを.cshrcに書く
    私はZSHを使っているため.zshrcに書いているが、デフォルト環境はcshのはずなので.cshrcに書けば成り立つと思う。
    echo PATH=${HOME}/perl5/bin:${PATH}
    
  3. local::libをインストールする
    local::libはroot以外にあるCPANモジュールを使うためのものらしい。これを自分のホームディレクトリ配下に入れるようにコマンドを流す。
    cpanm --local-lib=~/perl5 local::lib && eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib)
    

使い方

以下のようなコマンドを流すとパスを指定してCPANモジュールを取得・展開できる。

# 書式
cpanm -l <パス> <モジュール名>

例えば以下を流すとpwd配下にextlibディレクトリが生成され、IP::Geolocation::MMDBがダウンロードされて展開される。勿論、依存関係も勝手に解決してくれる。

cpanm -l extlib IP::Geolocation::MMDB

モジュールを利用するときはライブラリの配置されているルートを指定し、次にモジュール名を指定するとうまくいくようだ。

use lib './extlib/lib/perl5';
use IP::Geolocation::MMDB;

あとがき

https://cpanmin.us/を見に行くと以下の記述があり、辿ってみると日本の人が作っていてちょっと驚いた。

# This is a pre-compiled source code for the cpanm (cpanminus) program.
# For more details about how to install cpanm, go to the following URL:
#
# https://github.com/miyagawa/cpanminus
投稿日:

一年ぶりくらいにGitHub Actionsを使ってnpmパッケージを更新しようとしたら何かこけたので、通るようにした時にやったこと。

事象

なんかこんな感じのエラーが出てnpm publishに失敗した。

npm notice 1.6kB package.json
npm notice Tarball Details
npm notice name: @lycolia/ts-boilerplate-generator-cli
npm notice version: 0.28.1
npm notice filename: lycolia-ts-boilerplate-generator-cli-0.28.1.tgz
npm notice package size: 14.8 kB
npm notice unpacked size: 79.1 kB
npm notice shasum: 2ba5920ab2b46695bd996e86bf67b6a7cdb980cd
npm notice integrity: sha512-YHU4X10pXE7HP[...]RKmFhCpP4Gnmw==
npm notice total files: 25
npm notice
npm notice Publishing to https://registry.npmjs.org/ with tag latest and default access
npm notice Access token expired or revoked. Please try logging in again.
npm error code E404
npm error 404 Not Found - PUT https://registry.npmjs.org/@lycolia%2fts-boilerplate-generator-cli - Not found
npm error 404
npm error 404
npm error 404
npm error 404 Note that you can also install from a
npm error 404 tarball, folder, http url, or git url.

npm error A complete log of this run can be found in: /home/runner/.npm/_logs/2026-01-27T00_45_59_837Z-debug-0.log
Error: Process completed with exit code 1.

'@lycolia/ts-boilerplate-generator-cli@0.28.1' is not in this registry.

起きていたこと

  1. npmjsにあるAccess Tokensが全部消えてた
  2. Trusted publishing for npm packagesという話で、OIDC[1]を使ってGitHubと連携しないとGitHubなどのCI/CD、つまり自動化処理・バッチ処理からnpm publishできなくなっていた
  3. これに対応するためにはnpm CLI version 11.5.1か、それ以降が必要
  4. またnpmjsのOIDC連携をするためには2FA認証が必須で、そのためにはパスキーが必要
  5. Access Tokenは最大90日までになり、無制限のものはなくなった
    • Access Tokenでnpm publishを行う場合、2FAしていないとできない

対応に必要なこと

  1. npmjsに2FA登録する。要パスキー
  2. npmjsからGitHubリポジトリへのOIDC連携
  3. GitHubリポジトリにあるnpm publishしているyamlの修正
  4. npmのバージョンアップ

やったこと

  1. npmjsにパスキーを登録
  2. npmjsからGitHubリポジトリへのOIDC連携
    1. パッケージのSettingsタブを開き、Trusted publisherにリポジトリ情報を登録する
      Enviroment nameは第三者向けの項目[2]なので、設定しなくていい
    2. Set up connectionボタンを押して連携する
  3. GitHub Actionsのワークフローを直す
    1. permissionsの部分を足し、npm publishenvは不要なので削除
       name: npm publish on push to main
       on:
       push:
           branches:
           - main
      +permissions:
      +  id-token: write
      +  contents: read
       jobs:
       publish-to-npm:
           runs-on: ubuntu-latest
           steps:
           - uses: actions/checkout@v4
           - uses: actions/setup-node@v4
               with:
               node-version-file: '.nvmrc'
               cache: npm
               cache-dependency-path: package-lock.json
               registry-url: 'https://registry.npmjs.org'
           - name: npm continuous install
               run: npm ci
           - name: npm build
               run: npm run build
           - name: run publish
               run: npm publish
      -        env:
      -          NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH }}
      
  4. .nvmrcのNode.jsのバージョンをv24.13.0にした
    • このバージョンにすると自動的にnpmのバージョンが適合したため
  5. この後、GitHub Actionsを使ってnpm publishに成功すると、パッケージページやバージョン履歴ページには次のような表示が出るようになる

パスキー使いたくない問題

パスキーは端末依存で環境間共有の面倒さやGoogle縛りになるのが嫌などで入れたくない人も少なくないだろう。私もそう思う。正直パスキーありきのサービスは困る。

取り敢えず私の場合はEdgeを使っていて、Android Edgeへのパスキー共有はできないものの、いったんpublishするだけなら問題ないので、Edgeのパスキーを使うことにした。

どうしてもパスキーを使いたい、ほかの環境でも同期したいが、Googleに頼りたくない場合はKeePassXCというOSSを使うのが一つの手だ。

KeePassXCは暗号化されたデータベースファイルにIDとパスワードや、パスキーを記録するソフトウェアだ。複数環境への同期はNASなりSSHなり、クラウドストレージなどに鍵DBを保存して、それぞれの環境から読み書きすることで実現できる。

KeePassXCにはEdgeの拡張もあり、PCの中でKeePassXCを起動していればEdgeの拡張と同期してIDパスワードやパスキーを上手く管理してくれるようだった。ただ拡張機能が起動する前にKeePassXCが起動していないと手動で接続しないといけないなど、地味に面倒だった。

ただ調べた限りKeePassXCには公式のAndroidアプリがなく、サードパーティーアプリは毎回鍵ファイルを読みに行く実装のようで、オフライン時にも使うことのあるスマホでは運用に厳しさを感じた。逆に完全オフライン版もあるが、これはこれでAndroid側でDBを更新した時に別環境に飛ばすのが手間になるだろうからイマイチだ。

もう一つの選択肢としてはBitwardenを使う方法もある。こちらは公式がAndroidアプリを出しているほか、同期用のストレージを自前で用意する手間もかからない。

Bitwardenは、いわば1Passwordの無料版みたいな感じと言えるだろう。なお、BitwardenにはOSS版もあるので、セルフホスト運用もできるようだが、詳しくは調べていない。

私はBitwardenを軽く試してみたが、Edgeの自動入力機能に干渉しており、EdgeのパスワードDBを消し去らないとEdgeとBitwarden両方のパスワード入力補助が出てきて非常に邪魔だったことや、アニメーション設定をOFFにしても入力フィールドが飛び跳ねるアニメーションが出てきて嫌だったので、結局諦めた。せめて独立して動いてほしかった。

トラブルシューティング

Edgeで2FA認証しようとしたらパスキーがこけた

画像のような状態になった場合、何度かやり直すといけた。

「Two-factor authentication or granular access token with bypass 2fa enabled is required to publish packages.」と言われる

パスキーを使った2FAを登録することで解決した。

「npm error code EOTP」と言われる

npm notice Publishing to https://registry.npmjs.org/ with tag latest and default access
npm error code EOTP
npm error This operation requires a one-time password from your authenticator.
npm error You can provide a one-time password by passing --otp=<code> to the command you ran.
npm error If you already provided a one-time password then it is likely that you either typoed
npm error it, or it timed out. Please try again.

このようなエラーが出る場合、GitHub ActionsのWorkflowsのyamlファイルのルートに以下の記述が足りないので足す。

permissions:
  id-token: write  # Required for OIDC
  contents: read

「npm error code ENEEDAUTH」と言われる

npm error code ENEEDAUTH
npm error need auth This command requires you to be logged in to https://registry.npmjs.org/
npm error need auth You need to authorize this machine using `npm adduser`

このようなエラーが出る場合、npmのバージョンが古いので11.5.1以上にすることで解決する。

あとがき

正直npmjsを利用するのはもうやめようかと思う。ぶっちゃけもうNode.jsと積極的に関わりたくないし、代替技術を探したいと感じた。少なくともプライベートではそう思う。

TypeScriptに限界を感じてきたでも書いたが、Node.jsのエコシステム周りには甚だうんざりしてきており、今回の件でより、さらに、もっと嫌になった。

取り敢えず静的解析とかそういうのは欲しいし、テストもしたい、ほんでLinuxフレンドリーで、Windowsでも動いてくれるとなおよい。簡単なツールを書くことが多いのもありコンパイル言語よりはインタプリタ言語のほうが好ましい。

そう考えるとやはり浮かんでくるのはPHPになってくるあたり、21年前に触れた私の初めてのプログラミング言語との出会いに戻ってくるあたりが面白い。PHPerはPHPerから逃れられない宿命でもあるのかもしれない。

今は肩書上フロントエンドエンジニアとかいうのを仕事にしているが、そこまで性に合っていない気もしてきているし、Web系のバックエンドではPHPの仕事もまだまだあるので、これを機にPHPに戻るのも一つありだとは思った。

関係ない話、何ならadiaryのせいでTypeScriptよりPerlの方が好感を持ててきているまであるので、私はたぶんどうかしているのだと思う。


  1. OpenID Connectの略。いわゆるOAuthを使って別サービスのアカウントで連携処理を行うもの。
  2. OIDC連携はnpmjsのアカウントとリポジトリを紐づけるためのもので、publisher以外の人間が作業する場合に必要になるものと思われるが詳しくは調べていない。