Latticeのproject dashboardは既定でloopbackだけにbindし、bridge用socketや設定を作らない。
LAN上のreverse proxyなどから閲覧する時だけ、利用者がlisten IPを明示してbridgeを有効化する。
postinstallでは質問やnetwork公開を行わない。
lattice status --jsonとToDoの保存操作が、工程結果のstdoutに加え、stderrへ
lattice.dashboard_delivery.v1を返す。工程の保存成功を公開配信の成功と読み替えない。
| state | 確認できたこと |
|---|---|
local_only |
PC内の工程表が利用可能。reasonはbridge_unconfiguredまたはbridge_disabled |
bridge_only |
bridge設定を利用。hubが未設定のため、その先の公開配信は未確認 |
hub_delivered |
hubが対象工程を受理し、一覧にonlineで掲載し、工程HTMLの中継が成功 |
failed |
配信を確認できない。reasonとnext_actionで原因と復旧入口を返す |
有効なbridge設定がある端末では、工程操作が常駐設定・実行体・実processを確認し、欠落や実行pathのずれを
正規のbridge reconfigureで復旧してから配信を確認する。無効・未設定のbridgeは自動公開しない。
PCの初期化等で設定自体が失われた時はlocal_onlyがその事実を示す。公開先を推測して復元はしない。
明示の復旧入口lattice todo dashboard ensure --jsonは、ローカルdaemonから設定済みbridgeとhub配信までを
処理する。結果はlattice.todo_dashboard_ensure_result.v1で、activity.deliveryに同じ配信結果を含む。
配信失敗では非0を返す。通常の工程保存は配信故障で止めず、保存結果と配信失敗を別々に返す。
bridge setup/reconfigureにhubが設定されている場合は、dashboard起動、hubへの登録、一覧と工程HTMLの
確認までを成功条件とする。stdoutのlattice.bridge_cli_result.v4は維持し、stderrへ確認結果を返す。通信や中継が
失敗した時はBRIDGE_HUB_DELIVERY_UNCONFIRMEDで非0を返す。通信故障でも保存済みの接続設定は保持する。
hub URLは端末から直接到達する登録先を使う。reverse proxy経由の登録は接続元がproxyになり、hubから端末への
経路を誤る。別の公開HTTPS入口がある構成では、公開HTTPSとSSEの受入も本書後半に従って実施する。
配信確認は全OS共通とし、常駐の復旧はMacのLaunchAgent、WindowsのStartup Folderの既存入口を使う。 実行中の版だけが異なる場合は既存の自動更新に任せ、配信確認から再設定を強制しない。
TTYではlattice bridge setupで安全側既定の対話wizardを開始できる。最初の公開確認は既定で無効を選び、
cancelした場合は設定を変更しない。非TTYではhangせず、次の非対話commandを案内する。
portを省略するかautoにすると、49152–65535から候補を重複なく選び、
実際のexclusive bindとhealth確認に成功したportだけを保存する。
lattice bridge setup --listen 192.168.1.50 --port auto --dashboard --allow-host lattice.example.com --json--dashboardは現在のlocal dashboard descriptorをrequestごとに解決するため、dashboard再起動でportが変わっても
bridge設定はstaleにならない。固定upstreamを使う場合だけ--upstream http://127.0.0.1:4318を指定する。
listen IPは常に許可Hostへ入り、reverse proxyで公開するhostnameは--allow-hostを反復して追加する。
許可されていないHostは421となるため、DNS rebinding originへ工程情報を返さない。
lattice bridge status --json
lattice bridge reconfigure --listen 192.168.1.50 --port auto --dashboard --json
lattice bridge disable --json設定は~/.lattice/bridge.jsonへmode 0600でatomic保存する。setup/reconfigureは実bridge daemonが
選択socketをexclusive bindしhealthを返すまで成功にしない。起動に失敗した場合は旧設定へ戻す。
disableもbridge socketの停止確認後に成功し、loopbackのlocal dashboardは停止しない。
configまたはdaemon descriptorが壊れている場合、disableは公開socketのfail-closed停止を優先して破損control
fileを除去し、JSON結果のrecoveryへ処置を明示する。その後はsetupで再設定できる。
自動化・隔離testではabsoluteなLATTICE_CONFIG_DIRで設定rootを変更できる。無効な設定、低いport、
使用中の明示port、危険なrequest target、到達不能upstreamはsilent fallbackせずtyped errorを返す。
reachableは「設定したaddressで誰かが応答しているか」しか答えない。常駐設定(macOSのLaunchAgent、
WindowsのStartup launcher)が消えたbinaryを指していると、supervisorは起動できないprocessを回し続け、
どこにもエラーが出ないまま公開面から端末だけが消える。statusはそれを1回で名指しする。
lattice bridge status --jsonbridgeが無効な間は以下すべてnullで、bridgeを有効にしている時だけ観測する。
persistence — 常駐設定が実際に起動する対象。
| field | 意味 |
|---|---|
state |
installed/not_installed/unreadable |
loaded |
launchdへ読み込み済みか。Windowsには対応概念が無いのでnull |
node_path・node_exists |
起動するNode実行体と、それが今も存在するか |
bridge_path・bridge_exists |
起動するbridge scriptと、それが今も存在するか |
error |
unreadableのときだけtyped code(例BRIDGE_LAUNCH_AGENT_PLIST_UNSAFE) |
error=BRIDGE_PERSISTENCE_STATE_SPLIT |
launcherまたはplistの片割れだけが残った状態。手でfileを消さず、lattice bridge reconfigure --jsonで常駐設定を揃える(0.57.2以降) |
BRIDGE_PERSISTENCE_STATE_SPLITは、常駐設定の一方(macOSのplist、またはWindowsのlauncher/descriptor)だけが
存在することを示す。これは復旧対象そのものが壊れている状態なので、手作業でfileを削除せず、
lattice bridge reconfigure --jsonを実行して正規のinstall経路で両方を再生成する。
runtime — いま応答しているprocess自身の申告。stateはrunning/not_running/unattested/
descriptor_invalidで、running以外では各値がnullになる。runningでも、identityを返さない
0.55.0より前のdaemonが走っている間はversion以下がnullになる(この場合runtime_driftは空になり、
乖離の有無は判定できていない——「乖離なし」ではない)。
reconfigure直後は、identityの確認requestが400msで打ち切られるため一時的にunattestedを返すことが
ある(daemonの起動直後と競合する)。数秒おいて引き直せばrunningになる。続くようなら本物の不整合で、
reachableがtrueでも公開面は認証できていない。
| field | 意味 |
|---|---|
pid |
応答しているprocessのpid |
version |
そのprocessが読み込んでいるLatticeの版 |
node_path・node_version |
そのprocessを実行しているNodeの実体pathと版 |
bridge_path |
そのprocessが実行しているbridge script |
runtime_drift — 両者の食い違い。空配列は「差が無い」または「runtimeが名乗っていないので
判定できない」のどちらかである。
| 値 | 意味 |
|---|---|
bridge_path |
常駐設定と違うtreeのcodeが走っている(開発treeの残骸など) |
node_path |
常駐設定が指すnodeと実走nodeが別実体。焼くのは意図的にaliasなので、比較はrealpathで行う |
version |
npm更新後まだ旧moduleを保持している |
remedy — 自己解消しない状態にだけ、打つべきコマンドが入る。出るのは次の4つ。
persistence.node_existsまたはbridge_existsがfalse(起動対象が消えた)persistence.stateがnot_installed(bridgeは有効なのに常駐設定が無い。いま走っているdaemonが 最後の1つで、再起動しても戻らない)persistence.stateがunreadable(常駐設定を読めない)runtime_driftにnode_pathまたはbridge_pathがある
versionだけの差にはremedyを出さない。daemonは60秒ごとにon-diskのpackage.jsonと自分の版を
突き合わせ、差があれば自ら終了してsupervisorに新codeで起動し直させる。放っておいて最大1分ほどで
解消するので、コマンドを出す状態ではない。自己解消する差にコマンドを出すと、本物の障害が埋もれる。
remedyが出たらreconfigureで作り直す。plistやlauncherを手で書き換えない。
lattice bridge reconfigure --jsonmacOSのLaunchAgent載せ直しでは、launchctl bootoutのexit 0はunload受付だけである。labelが
domainから消える(launchctl printが113)前にbootstrapすると、launchdは
5 Input/output errorを返す。reconfigureはprint 113を待ってから載せる(0.60.7・
ADR 0179)。失敗時は
launchctlのexit codeとstderrがlattice.cli_error.v2のdetailに残る。
hubへ繋いでいる端末では、runtime.last_heartbeatが最後にhubへ名乗った結果を持つ。公開一覧で
自分のprojectがofflineになっている時、原因が端末側か配線側かはここで分かれる。
state |
意味 | 打つ手 |
|---|---|---|
accepted |
全件受理された。公開面に出ていないならhub側の可視性設定を見る | — |
partial |
一部が他の生きた端末に所有されている。rejected_projectsが名指しする |
意図した端末なら放置。奪うならadopt |
rejected |
hubがrequest全体を拒否した。detailにtyped code |
detailのcodeで分岐 |
unreachable |
hubへ届かない。配線かhubの停止 | hubのURLと生死を確認 |
skipped_no_projects |
配信しているprojectが0件。名乗るものが無い | 正常な静止。公開したいならそのrepoで作業する |
skipped_no_dashboard |
dashboard daemonを観測できない。配信そのものが立っていない | daemonの生死を見る。todo系commandを1回打てば起動する |
null |
hub未設定、またはまだ1回も送っていない | — |
名乗る集合はdaemonが実際に配信している集合そのもの(ADR 0165)。配信集合は
last_seen_atが1週間以内、またはactive run、または監査待ちPhaseがあるprojectである。
人がCLIを叩かなくても、その条件を満たす限り公開面に残る。1週間を超え、runも監査待ちも
無いprojectは配信から外れる。heartbeatの90秒TTLは、配信そのものが止まった時
(daemon停止)にofflineへ落とすためのもので、鮮度窓ではない。
常駐設定へ焼くnodeのpathは、版付きの実体(homebrewのCellar/node/<version>/bin/node、nvm-windowsの
版ディレクトリ)ではなく、同じbinaryを指すとrealpathで検証できた安定alias(/opt/homebrew/bin/node、
C:\Program Files\nodejs\node.exe)を選ぶ。brew upgrade nodeが旧versionのディレクトリごと消しても
起動対象が残るようにするためである。
安定aliasを検証できない環境(shim方式のasdf/volta等。shimは自身のlauncherへ解決されるので実体と
一致しない)では、版付きpathのまま焼く。検証していないpathを推測で焼けば別のnodeでdaemonが起動して
しまうためで、そこでの防御は起動継続ではなくnode_existsによる消滅の可視化である。
0.55.0より前に設定した常駐は自動では移行しない。 焼き直しは
reconfigureを実行した時にだけ 起きるので、Latticeを更新しただけの端末は版付きpathを抱えたままになる。更新後に各端末でlattice bridge reconfigure --jsonを1回打つ。現在どちらを焼いているかはpersistence.node_pathで読める。
なおsetup/reconfigureをnode_modules配下でない実体(開発tree)から実行すると、結果のwarningsへ
BRIDGE_PERSISTED_FROM_DEVELOPMENT_TREEが入る(該当しなければ空配列)。そのtreeを動かすと常駐が
止まり、npm更新も反映されない。開発treeから常駐させること自体は正当な操作なので拒否はしない。
設定したlisten IPがホストから消えると、古いsocketは死んだアドレスへ取り残され、LANから到達できなくなる。
daemonは各reconcileで実効アドレスを解決し直し、同一subnet(IPv4 /24、IPv6 /64)に生きたアドレスがあれば
そこへbindし直す。VPNや別NICなど異なるnetworkのアドレスは採用せず、候補が無ければ
BRIDGE_LISTEN_ADDRESS_ABSENTで公開socketをfail-closedにする。再bind先は許可Hostへ自動で加わる。
LATTICE_BRIDGE_REGISTRAR_SSH_HOSTとLATTICE_BRIDGE_REGISTRAR_SCRIPTを両方設定すると、新しいbindingを
張るたびにssh <host> <script> <port>でreverse proxy hostへ自己登録する。アドレスは送らず、remote側が
ssh送信元から決めるため、各hostは自分自身しか登録できない。登録の失敗はbridgeを落とさずstderrへ
typedに報告する。この配線が無いと、Caddy等が持つリテラルはlease変更のたびに黙って陳腐化する。
reverse proxy hostへsshで到達できるなら、LANへbindせずloopbackだけで公開できる。bridgeが動くhostから 接続しに行くため、そのhostのLAN addressはreverse proxyのどこにも現れず、追従も自己登録も不要になる。
lattice bridge setup --listen 127.0.0.1 --port 53939 --dashboard --allow-host lattice.example.com --json
ssh -N -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 \
-R 172.18.0.1:53939:127.0.0.1:53939 proxy-hostreverse proxyはこの固定endpointだけを見る。転送口のbind先は、reverse proxyが到達できるaddressにする。
Docker上のreverse proxyでは、containerの127.0.0.1はcontainer自身のloopbackでありhostのそれではないため、
hostのloopbackへ開いた口には届かない。対象networkのgateway(docker network inspectのGateway)へbindする。
sshdは既定のGatewayPorts noだと127.0.0.1にしかbindできない。clientspecifiedにすると、clientが明示した
addressだけにbindする(yesと違い全interfaceへは晒さない)。host firewallがINPUTをDROPしている場合は、
その1 portだけを許可する。ExitOnForwardFailure=yesは、転送口を開けないまま接続だけ生かす状態を防ぐ。
常駐はprocess supervisorのKeepAliveに任せ、切断時は張り直す。
bridgeを有効化したMacとreverse proxy hostの間で、まず許可Hostを付けたLAN到達を確認する。 この段階が失敗している時はDNSやTunnelを追加しない。
curl --fail --header 'Host: lattice.example.com' \
http://MAC_LAN_IP:BRIDGE_PORT/projects/Caddyは既存のDocker networkと証明書運用を維持し、Lattice用siteだけを追加する。
lattice.example.com {
reverse_proxy MAC_LAN_IP:BRIDGE_PORT {
flush_interval -1
}
}本番反映はcontainer内でcaddy validateを通してからcaddy reloadする。Caddyfileを単一ファイルで
bind mountしている構成では、atomic renameでhost側fileを置換するとcontainerが旧inodeを参照し続ける。
更新前backupを残し、inodeを維持するin-place更新を使うか、directory bind mountへ変更する。
remote-managed Cloudflare Tunnelでは、Tunnel実行tokenを設定APIの代用にしない。Cloudflareの正規管理面で
public hostname lattice.kitepon.dev を次のoriginへ対応付ける。
- Service:
https://caddy:443 - TLS Origin Server Name:
lattice.kitepon.dev(管理面に同等の設定がある場合はMatch SNI to Hostでもよい)
http://caddy:80は選ばない。CaddyのHTTPからHTTPSへのredirectをTunnelがorigin応答として返す構成は、
外部requestが同じ公開URLへ戻るredirect loopになり得るためである。外部gateではredirectを追って200にせず、
最初の応答がHTTPSの200であることを確認する。
受入は次の3 gateを独立して記録し、後段の成功で前段を代用しない。
- LAN bridge: reverse proxy hostから許可Host付きで
http://MAC_LAN_IP:BRIDGE_PORT/projects/が200。 - Docker Caddy: Caddyへ
Host: lattice.kitepon.devを付けたHTTPS requestが200。証明書検証を省略する 内部probeを外部公開成功の証拠にはしない。 - Cloudflare public HTTPS:
https://lattice.kitepon.dev/projects/がredirectなしで200となり、一覧から開いた/projects/<project_id>/のHTML titleがLattice — <project名> 依存工程図である。
公開viewerの404も、ブラウザとAPIの両契約を別々に確認する。未知URLへAccept: text/htmlを
付けたrequestはHTTP 404かつContent-Type: text/htmlで、noindex, nofollowと
/projects/、https://kitepon.dev/への戻り先を持つ。Accept: application/jsonでは
HTTP 404かつContent-Type: application/jsonで、既存の
lattice.todo_gantt_http_error.v1を返す。
curl --silent --show-error --include --header 'Accept: text/html' \
https://lattice.kitepon.dev/unknown
curl --silent --show-error --include --header 'Accept: application/json' \
https://lattice.kitepon.dev/unknown外部gateはHTMLだけで閉じず、各projectの
https://lattice.kitepon.dev/projects/<project_id>/eventsも確認する。応答は200かつ
Content-Type: text/event-streamで、接続直後にevent: stateと現在のhead_digestを返さなければならない。
接続を開いたまま正規のTodo更新を行い、新しいstateが同じstreamへ届くことを確認する。切断後に再接続しても
再び初回stateが届き、そのdigestが最新headと一致することまでを継続・再接続gateとする。
curl --fail --show-error --include --no-buffer --max-time 15 \
https://lattice.kitepon.dev/projects/PROJECT_ID/events