ken_nogi/NodeSrv/apps/xwiki/docs/2026-08-12-construction-log.md
Kenichiro NOGI ce58cb4be4 初回コミット: dev配下(NodeSrv/Pleasanter等)をGitea管理下に統合
GitHub(nextgroup2706/ken_nogi)は今後使わず自社Gitea運用に切替え。
NodeSrvは旧リポジトリの履歴を破棄しファイルのみ統合(Dokploy用サービスアカウントは
別途mygit-admin/NodeSrv.gitに履歴あり)。notepmエクスポート(12GB)とPleasanter
インストーラzip(208MB)はサイズが大きいため.gitignoreで除外。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 15:37:06 +09:00

295 lines
33 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# XWiki構築ログ 2026-08-12
対象: XWiki(`xwiki55.next-hd.net`、Node B稼働)。5要件のうち要件1〜3完了、要件5(NotePM移行)は全量インポート完了(ネスト構造・添付プレビュー・リスト構造対応済み)、差分同期スクリプトのみ未実装。OIDCユーザー事前登録は方針保留(作成分は削除済み)。要件4は保留継続。
---
## 追記(同日後半): 全量インポート・OIDCユーザー事前登録に着手、途中中断
### NotePM全量インポート
`import-notepm.js`を全ノート一括対応(`--all`)・サブフォルダ再帰対応に拡張し、76ート・1042ページの本実行を開始。**ユーザー指示により421/1042ページ処理時点で中断**(taskkillでnodeプロセス強制終了)。処理済み分はXWikiに反映済み、スクリプトはPUTベースで冪等なため次回`--all`で再実行すれば未処理分だけ進む(既存ページは上書きされるだけで害はない)。
### ユーザー自然作成の抑制 + Keycloak同期ユーザーの事前登録
**要件**: LINEWORKS SSO初回ログイン時にXWiki OIDC拡張が「未知のユーザー」と判定して勝手に新規アカウントを作る挙動(自然作成)を止め、代わりにKeycloak(nexthdレルム、org-master-syncで同期済み351名)のユーザー情報を使って事前にXWikiアカウントを用意し、ログイン時にはその既存アカウントへ紐付けたい。
**判明した仕組み(拡張ソース調査+実機の既存ユーザーオブジェクトを直接確認して特定)**:
- 既存ユーザーの検索キーは`XWiki.OIDC.UserClass`オブジェクトの`issuer`+`subject`プロパティの組み合わせ(`OIDCUserManager#updateUser`が`store.searchDocument(issuer, subject)`で検索)
- `issuer`はKeycloakのレルムURL(`https://auth91.next-hd.net/realms/nexthd`)、`subject`はKeycloakのユーザーUUID(`GET /admin/realms/nexthd/users`の`id`、IDトークンの`sub`クレームと一致)。実機で`kenichiro.nogi`ログイン済みユーザーのオブジェクトを見て確認・実証済み
- `oidc.enableUser=false`は「新規作成させない」設定ではない。新規ユーザーは作成されるが`XWiki.XWikiUsers.active=0`(非アクティブ)になるだけ。真の抑制は「事前登録して`searchDocument`にヒットさせる」以外に手段がない(拡張側に新規作成を拒否する分岐は無い)
**実装**: `apps/xwiki/scripts/provision-oidc-users.js`新規作成。
1. Keycloak Admin API(`GET /admin/realms/nexthd/users?max=1000`)で全351ユーザー取得
2. Groovyページ経由で`XWiki.OIDC.UserClass`の既存`subject`一覧を1回のXWQLクエリで取得(**REST Query API `/rest/wikis/xwiki/query`はBasic認証だと常に空を返す不具合を実機で確認、Groovy経由に回避**)
3. 未登録ユーザーについて、XWikiに`XWiki.<username(ドット除去)>`ページを作成→`XWiki.XWikiUsers`オブジェクト(first_name/last_name/email/active=1)→`XWiki.OIDC.UserClass`オブジェクト(issuer/subject)の順で設定。1ユーザーあたり5リクエストで完結(オブジェクト全体を`<property>`複数含むXMLで一括PUT可能、実機で確認済み)
351名中86名処理した時点で**ユーザー指示により中断**(taskkillでnodeプロセス強制終了)。処理済み分は正常にXWikiへ反映されており、スクリプトは重複実行しても安全(処理前に必ず既存subject一覧と突き合わせてスキップするため)。次回`node scripts/provision-oidc-users.js`を引数無しで再実行すれば残り265名を処理する。
**未実装(次回の課題)**:
- `oidc.enableUser=false`の設定投入(事前登録漏れの未知ユーザーが来た場合の非アクティブ化、二段構えの安全策として)
- 事前登録ユーザーが実際にOIDCログイン時に新規作成されず既存アカウントへ紐付くかの実機確認(現状は理屈と実装のみ、ログインテスト未実施)
- 全351名の事前登録完了
- NotePM全量インポートの残り(621ページ)
- **インポート後のフォルダ構造が復元されていない**: `import-notepm.js`は現状、ノート内のサブフォルダ(例: 「検証用ノート」内の「帳票(ISO)※ISO重複のため確認後消去予定/」)を無視し、サブフォルダ配下のページも同じノートのスペースへフラットに配置する設計のまま実装していた。実際にインポートした結果を見ると階層情報が失われて使いにくく、**次回再調整が必要**(サブフォルダをXWikiのネストスペース、またはページの親子関係にマッピングする対応を検討)
## 追記: 右パネル目次表示、断念
NotePMのUI(右カラムに目次表示)を参考に、XWiki標準の「パネル」機能で同等の目次パネルを実装しようとしたが**断念、自作パネルは撤去済み**。
- 自作実装(Groovy+`HeaderBlock`直接抽出): 動作はしたが「無理やり感がある」としてユーザー指示により撤去
- 標準的な実装方法(XWikiフォーラムで紹介されている`{{context document="..." transformationContext="document"}}{{toc/}}{{/context}}`方式)を試したが、**同じ「block HTML content cannot be displayed inline」エラーが再発**。このXWiki 17.10.4のPanels実装(`PanelWikiUIExtension`)が、パネルコンテンツを厳格にインラインHTMLモードでしか処理できない制約を持っている模様
- 拡張リポジトリにもTOC専用パネル拡張は見当たらず。標準機能・既存プラグインでの解決策なし、棚上げ
`rightPanels`設定・作成した`Panels.TableOfContents`ページとも削除済み、現状は元の3パネル構成(ヒント/最近の修正/サポート)のまま。
## 要件1: mail-infra連携
**症状**: Node B→Node A間のDocker Swarm overlay network不通。Node BのSwarmメンバーシップ内部アドレスが停止済み旧インスタンスIPに固着、`swarm join-token`が誤ったIP返却。
**対処**: Swarm自体の完全復旧は深追いせず、代替経路採用。
1. mail-infra(postfix-relay)のdocker-compose.ymlにホストポート公開追加: `ports: - "172.26.11.222:587:587"`(Node AプライベートIP限定)。Dokploy API(`compose.update`/`compose.deploy`)経由で適用
2. Node B→Node A直接IP経由でSMTP疎通確認(`220 ... ESMTP Postfix`受信)
3. XWiki側`Mail.MailConfig`ページ(クラス`Mail.SendMailConfigClass`)をGroovy経由で設定: host=172.26.11.222, port=587, from=xwiki-admin@next-hd.co.jp, properties=`mail.smtp.starttls.enable=false`
4. テストメール送信成功、実際にxwiki-admin@next-hd.co.jp着信確認
**教訓**: Dokploy API経由の設定変更で、TLS無効化等の文言を含むcurlコマンドがBashツールのauto modeクラシファイアにブロックされることがある。PowerShell経由の`Invoke-RestMethod`なら通る。
---
## 要件2: 添付ファイル検索
XWiki標準の組み込みSolr+Tika連携で**既に実現済み**と判明。追加設定不要。
検証方法: テストページに添付ファイル(txtファイル、ユニーク文字列入り)をアップロード→標準検索フォーム(`/bin/view/Main/Search?text=...`)で検索→ヒット確認。バックエンドのSolrクエリ(`attcontent`フィールド)でも直接ヒット確認済み。
---
## 要件3: LINEWORKS SSO(Keycloak OIDC)
### 構成
```
[社員] → XWikiアクセス → Keycloak(nexthdレルム、client=xwiki)へリダイレクト
→ Identity Provider Redirector → LINE WORKS(SAML)へ自動リダイレクト
→ LINE WORKSでログイン → XWikiにログイン完了
```
### 実装
1. Keycloak nexthdレルムに新規OIDC Client `xwiki`作成(confidential、redirectUris: `https://xwiki55.next-hd.net/*`)
2. XWikiに拡張`org.xwiki.contrib.oidc:oidc-authenticator` v2.25.2インストール(Extension Manager、Markdown拡張と同じ手法)
3. `xwiki.properties`にOIDC設定
### ハマりどころ(重要、今後のXWiki作業で必読)
**1. xwiki.propertiesが非永続領域**
実体は`/usr/local/tomcat/webapps/ROOT/WEB-INF/xwiki.properties`で、永続化ボリューム`xwiki_data:/usr/local/xwiki`の対象外。設定を直接編集しても次回コンテナ再作成で消える。
→ docker-compose.ymlに`xwiki_webinf:/usr/local/tomcat/webapps/ROOT/WEB-INF`を追加(named volume、既存内容は自動コピーされ安全)。
**2. XWiki 17.x系は認証設定キーが変わっている**
拡張の一般的なドキュメントにある`xwiki.authentication.authclass=org.xwiki.contrib.oidc.auth.OIDCAuthServiceImpl`はもう効果がない(レガシーキー)。
正しいキー: `security.authentication.authService=oidc`(値はhint名。`cm.getComponentDescriptorList(Class.forName("org.xwiki.security.authservice.XWikiAuthServiceComponent"))`で確認可能、標準=`standard`、OIDC拡張=`oidc`)。
**3. oidc.scopeはカンマ区切り必須**
`oidc.scope=openid profile email`(スペース区切り)は設定パーサーがリスト分割してしまい、Nimbus OAuth2ライブラリが「openidが含まれない」と判定してエラー(`IllegalArgumentException: The scope must include an "openid" value`)。ログイン試行毎に例外発生、401/Access Deniedが不安定に発生する原因になった。
正: `oidc.scope=openid,profile,email`
**4. oidc.tryLocal=trueでBasic認証と両立**
`security.authentication.authService=oidc`だけだとBasic認証(REST API管理作業用)が完全無効化され、緊急時のロールバック手段を失う。`oidc.tryLocal=true`併用でBasic認証は維持したまま、ブラウザログインはOIDCへ自動リダイレクトされる状態にできる。
**5. Client単位のフローオーバーライドでプリザンター無影響**
要件「ログイン画面にユーザー名/パスワード欄を出さず直接LINE WORKSへ」の実現方法。レルム全体の`browser`フローを変更するとプリザンター(同じnexthdレルムのSSO)にも影響するため、Client単位でオーバーライド。
1. `POST /admin/realms/nexthd/authentication/flows/browser/copy`で`browser`フローを複製(`browser-xwiki-idponly`)
2. 複製フロー内の`forms`サブフローを`DISABLED`に
3. 同フロー内`Identity Provider Redirector`のexecution configに`defaultProvider=lineworks`設定(**これが無いと単純にformsをDisabledにしただけでは「We are sorry... Invalid username or password」エラーになる**、単一IdPでも自動リダイレクトはconfigなしでは発動しない)
4. `xwiki`クライアントの`authenticationFlowBindingOverrides`に`{"browser": "<新フローID>"}`を設定
結果: XWikiは中間選択画面なしで直接LINE WORKSへ、プリザンターは従来通り。
**6. xwiki-adminのブラウザログイン手段**
`security.authentication.authService=oidc`が有効だと標準ログインページに到達できないが、`OIDCClientConfiguration`の`oidc.skipped`はリクエストパラメータからも読める設計。
`https://xwiki55.next-hd.net/bin/login/XWiki/XWikiLogin?oidc.skipped=true` で標準ログインフォームにフォールバックできる。管理者用に控えておくこと。
### 副次的に再発したDocker Swarm障害
WEB-INF永続化のためXWikiコンテナ再作成時、Node Bのoverlay network接続断が再発(Traefik・xwiki-dbともエイリアス消失、`docker network connect --alias <name> dokploy-network <container>`→restartで復旧)。Node Bを一度でも`swarm leave`→`join`し直すと高確率で起きる。Swarm方式自体の是非は別途要検討([[../../docs/project_dokploy_multiserver_plan|Dokployマルチサーバー方針メモリ]]参照)。
---
## 要件5: NotePMデータ移行(調査・試験移行)
### エクスポートデータの構造
`notepm/export_nextgroup_20260807020526491/`配下、76ート・1042ページ(.md)・4753添付ファイル。
- 各mdファイルはYAMLフロントマター付きMarkdown(`title`, `register_date`, `register_user`, `update_date`, `update_user`, `attachments`リスト)
- フォルダ名/ファイル名は`表示名_ハッシュ10桁`形式。このハッシュはNotePM本体の`note_code`/`page_code`と完全一致(API調査で確認済み)
- 添付ファイルは本文中で**NotePM本体の元URL**(`https://nextgroup.notepm.jp/private/<uuid>.<ext>`)のまま参照されており、ローカル`_添付`フォルダへの相対パスではない。frontmatterの`attachments`リストに「ファイル名+元URL」対応表がある
### 一括インポートスクリプト
`apps/xwiki/scripts/import-notepm.js`
```
node scripts/import-notepm.js <ノートディレクトリ> --root=<エクスポートルート全体> [--space=スペース名] [--dry-run]
```
処理内容:
1. .envから`XWIKI_BASE_URL`/`XWIKI_ADMIN_USER`/`XWIKI_ADMIN_PASSWORD`読み込み
2. frontmatter簡易パーサー(汎用YAMLではなくNotePMエクスポート専用)
3. `<img>`タグ前後に空行を強制挿入(下記バグ対応)
4. 絵文字ショートコード(`:mag:`等)→Unicode絵文字変換
5. `--root`配下を全走査してノート/ページのハッシュ→(スペース名, ページ名)マッピングを構築、本文中のNotePM内部リンク(`/note/<hash>/`, `/page/<hash>`)をXWiki実URLへ置換
6. ページ作成(空本文で先に作成)→添付ファイルアップロード→本文中のattachments URL置換→本文更新、の順(添付ファイルは存在しないページにアップロードできないため順序が重要)
### 発見した不具合と対策
**`<img>`タグのレイアウト崩れ**: NotePM原本は`<img>`タグ直前に空行が無いことが多く、CommonMarkが直前の段落と結合して「画像がテキストの右に回り込む」崩れになる。`<img>`前後に空行を強制挿入して独立ブロック化することで解消。
**絵文字ショートコード**: 全体で16件程度(`smile`, `mag`, `beginner`等10種)、ほぼ0_NotePMマニュアル(定型ページ)に集中。変換テーブルで対応。
**添付ファイル未対応リンク**: frontmatterの`attachments`リストに載っていない本文中の直リンクは変換されない。API方式に切り替えれば`file_id`ベースで確実に解決できる見込み(下記)。
### サンプル移行実績
- 「0_NotePMマニュアル」12ページ + 添付3件: 成功
- 「検証用ート」直下5ページ(内部リンク含む): 成功、内部リンクが正しくXWiki実URLへ変換されることを実証(例: `page/97b07418bc``002_西東京建設/業務フロー(東京IC)`のURL、アンカー`#見出し`も保持)
### NotePM本体API調査(差分同期用)
ベースURL: `https://{team}.notepm.jp/api/v1`、Bearer token認証、レート制限60req/min。
APIトークン発行: NotePM右上ユーザーメニュー→「個人設定」→「APIアクセストークン」→「新規作成」(オーナー/管理者権限必要、読み取り専用スコープ推奨)。`apps/xwiki/.env`に`NOTEPM_TEAM_DOMAIN`/`NOTEPM_API_TOKEN`として保存(gitignore対象)。
確認済みエンドポイント(実データで動作確認済み):
| エンドポイント | 用途 |
|---|---|
| `GET /api/v1/notes` | ノート一覧。`note_code`はエクスポートのフォルダ名末尾ハッシュと一致 |
| `GET /api/v1/pages?note_code=xxx&per_page=100` | ページ一覧、ページング対応。`updated_at`含む(差分検出の要) |
| `GET /api/v1/pages/:page_code` | ページ詳細本文。`page_code`はファイル名末尾ハッシュと一致 |
| `GET /api/v1/attachments?page_code=xxx` | 添付ファイル一覧。`file_id`, `file_name`, `download_url` |
| `GET /api/v1/attachments/download/:file_id` | ダウンロード。200・正しいcontent-type/サイズで確認済み |
**重要**: 添付ファイルの元URL(`/private/<uuid>`)はBearerトークンでは認証不可(Webセッション用Cookie認証が必要、302でログインページへ)。添付ファイルは必ず専用API(`/api/v1/attachments`系)経由で扱うこと。
### 差分同期の設計方針(実装未着手)
1. `page_code → updated_at`の対応表をローカルJSON等で保持(初回は空)
2. 定期実行時、全ノート→全ページの`updated_at`を取得し前回値と比較、差分のみ処理
3. 差分ページ: 本文取得→添付ファイル一覧取得→ダウンロード→XWikiアップロード→本文内URLを`file_id`ベースで置換(frontmatter方式よりファイル名の取り違えリスクが低い)
4. 全ページ一覧と前回記録の突き合わせで削除ページも検出可能
5. `note_code`/`page_code`がそのままハッシュとして使えるので、一括インポートスクリプトの内部リンク解決ロジック(ハッシュ→XWikiページマッピング)はAPI方式でもほぼ流用できる
一括インポート(ファイルベース)と差分同期(API方式)は、入力元が違うだけで変換ロジック(Markdown処理・img修正・絵文字変換・添付処理・内部リンク解決)は共通化できる設計。次の実装ではこの共通化を優先する。
---
## 追記(同日続き): ネスト構造再現・添付プレビュー・リスト構造修正・全量反映
### サブフォルダ構造→XWikiネストページで再現
前回課題(フォルダ構造未復元)対応。`import-notepm.js`大幅改修。
- `collectMdFiles`: サブフォルダ再帰収集、`spacePath`配列(ノート名+サブフォルダ階層)を各ページに付与
- REST URL: `spaces/A/spaces/B/pages/C`とspacesチェインしネストページ作成
- `ensureWebHomePages`: 全階層の祖先パス集約、`WebHome`(ネストページの代表ページ)未存在なら`{{children/}}`入りで新規作成。子ページ動的ツリー表示用
- 検証: 001ートのみで構造再現確認→問題なし→**既存全ページ削除後、階層対応版で全1042ページ再インポート**(WebHome 309件生成)
### OIDCユーザー事前登録、方針転換で全削除
ユーザー指示「作成取消。xwiki-admin以外削除」。86名分事前登録済みだったが全削除実施。`provision-oidc-users.js`はリポジトリに残置(方針保留、再開時は前回記録のissuer/subject方式そのまま使える)。
### 添付ファイル改行・プレビュー機能
- **改行問題**: `[text](url)`単独行がMarkdown上単一改行で連続→CommonMarkが同一段落結合→横並び表示化。行末に半角スペース2つ(hard break)付与で解消
- **Office文書プレビュー**: `{{office attachment="X" filterStyles="false"/}}`。doc/docx/xls/xlsx/ppt/pptx/odt/ods/odp対象。XWikiコア機能(`xwiki-platform-office-macro`)、追加インストール不要、サーバー内蔵LibreOffice連携
- **PDFプレビュー**: `{{pdfviewer file="X"/}}`。`org.xwiki.contrib:xwiki-macro-pdfviewer` v1.7.2(Mozilla pdf.js)、要インストール
- インストール中Extension Manager `job.join()`ハング発生。原因調査中、両Lightsailードとも**SSH(22番)不通**判明→ユーザーから「会社グローバルIP以外SSHをFWで弾く設定」との説明→VPN接続でSSH復旧→XWikiコンテナ再起動(`docker restart xwiki-document-server-38jpgp-xwiki-1`)でExtension Manager正常化、インストール成功(12秒でFINISHED)
- 併せて動作確認できなかった`ludovic:addpreviewlinks`拡張はアンインストール
- `attachmentMacroFor(filename)`関数追加、拡張子判定してoffice/pdfviewerマクロ文字列返す
- 既存ページ更新専用の`--update-only`モード新設(ページ作成・添付再アップロードせず本文のみ再構築・PUT)。全1042ページに一括適用、1395件マクロ挿入・0件エラー
### 「・」箇条書きリスト構造修正
**症状**: NotePM原本の「・」始まり(全角/半角スペース混在インデントでネスト表現)箇条書きが、XWiki側で改行もリスト構造も失われ1段落に結合。ユーザーがNotePM画面とXWiki画面のスクショ比較で指摘。
**原因**: `import-notepm.js`に「・」記法→CommonMarkリスト変換処理が存在しなかった(未実装のまま気付かれず全量インポートしていた)。
**対応**: `convertBulletLists(text)`関数新設。
- 行頭の全角/半角スペース混在インデントを幅計算(全角=2、半角=1)し、空行区切りブロック単位でユニーク幅を昇順ランク付け→ネスト深さとして採用(NotePMインデント幅が厳密なタブ単位でないため絶対値でなく相対順序で近似)
- 「・」の無いインデントのみ行もブロック継続中なら子項目として扱う(NotePM側は記号なし行もリスト表示されているため)
- コードブロック(` ``` `)内は対象外
**デバッグでハマった点**: 単体テストで正規表現`/^([  ]+)(.*)$/`がヒットせず。原因はNotePM原本の改行が`\r\n`で、split("\n")後も各行末に`\r`が残存、JS正規表現の`.`はデフォルトで`\r`にマッチしないため`$`アンカーに到達できずマッチ全体が失敗していた。関数冒頭で`text.replace(/\r\n/g, "\n")`して解決。
実データ「【デザイ】業務フロー営業」で検証→レンダリング後HTML(`<ul><li>`ネスト)で構造再現確認→全1042ページに`--update-only`で一括適用、0件エラー。マクロ重複懸念(`--update-only`は毎回mdファイルから本文再構築するため理論上重複しないはずだが)も既適用済みページで再検証、重複無し確認。
### 添付ファイル名に半角丸括弧を含むページでリンクが壊れる不具合
**症状**: ユーザー指摘「doc、xlsの表示がプレビューのようになっていない」。「【デザイ】登記依頼方法」ページ(添付: 登記依頼シート【デザイノ様】(丸山リーガル) (2).xls)のレンダリング結果を確認したところ、`[filename](url)`というMarkdownリンク構文がリンク化されずプレーンテキストのまま出力されていた(officeマクロ自体は正常に変換されテーブルは表示されていたが、その直前のリンクテキスト表示が壊れていた)。
**原因**: `import-notepm.js`が添付URL等を組み立てる際に使っていた`encodeURIComponent`は、JS仕様上`(`,`)`,`!`,`'`,`*`をエンコードしない(RFC3986非予約文字のため)。そのURLをMarkdownの`[text](url)`構文にそのまま埋め込むと、URL中に残った生の丸括弧がリンクの終端`)`と誤認識され、構文全体が崩れてプレーンテキスト化していた(実機のXWiki markdown/1.2レンダラーで確認)。角括弧`[]`や全角丸括弧()は問題なく、半角丸括弧`()`を含むファイル名・ページ名のみで発生。
**対応**: `encodeUrlForMarkdownLink(s)`ヘルパー新設(`encodeURIComponent`後に`()!'*`を追加で`%XX`エンコード)。Markdownリンクの宛先として使う3箇所(添付ダウンロードURL、ページ内部リンクURL、ートURL)全てをこのヘルパーに置き換え。丸括弧を含む添付ファイルの複数ページ(登記依頼方法、66添付ファイルを持つ「営業③ 邸別仕様書」等)で修正確認後、**全1042ページに`--update-only`で再度一括適用、0件エラー**。
**深さ判定ロジックの再改善(ユーザー指摘で気付いた問題)**: 初版は「全角=2、半角=1で幅計算しユニーク幅を昇順ランク付け」方式だったが、実際のXWikiレンダリング結果(スクショ)をユーザーが確認したところ、意味的に並列であるべき項目(例:「展示場接客」「来場予約」「WEB相談」は全て「初回対応」の直接の選択肢)が誤って親子関係になってしまっていた。原因は、NotePM原本のインデントが「・」項目ごとに半角スペース数が不規則に増減する(コピペ等のノイズ)一方、全角スペースの個数だけは論理的なネスト深さと一致していたこと。
対応: 深さ判定を「・直前の全角スペース個数のみ」に変更、半角は完全無視。さらに「・」の無い行(インデントのみの注記・説明文)は、それ自身のインデント幅では判定せず常に直前の「・」項目の子(深さ+1)固定とした(連続する複数の「・なし」注記行が段々ネストが深くなっていく誤りを解消)。同ページと空行区切り単発「・」項目のみのページ(パートさん業務内容等)双方で再検証し副作用無し確認→**全1042ページに再度`--update-only`で反映、0件エラー・マクロ重複無し**。
## 追記(2026-08-13未明): XWikiサービス停止→Node B完全障害→Remote Servers方式へ移行
### 発端: OOM Killによるサービス停止
リスト構造修正の全ページ適用直後、ユーザーから「ページが1つも開かない」と障害報告。調査の結果、直前に検証で開いた66添付ファイルの大量ページ`000_ISO/業務手順書・帳票(ISO_NM-201)/営業③ 邸別仕様書`でのoffice/pdf一括プレビュー変換がNode B(4GB RAM)のメモリを使い切り、XWikiコンテナがOOM Killされていた`docker inspect`で`OOMKilled: true`確認)。
### 復旧試行で発覚した本丸: Node BのSwarm raftアドレス固着
`docker start`で単純復旧を試みたが`Could not attach to network ... context deadline exceeded`で失敗。追跡した結果、**Node B(Swarm worker)がNode A(manager)に接続できない状態**と判明。原因はNode AのプライベートIPがDHCPにより`172.26.8.247`→`172.26.11.222`へ変わっていたが、Swarmの内部管理情報`ManagerStatus.Addr`、join token埋め込みIPは古いIPのまま固着していたこと。`docker swarm init --force-new-cluster --advertise-addr <new-ip>`を試すもaddrは更新されずSwarm仕様上、raftのadvertise addressは初期化時のみ設定可能で後から変更不可、`docker node update`にもaddr変更オプションなし)。
これは同日昼間に発生していた「Node Aのmail-infraとNode BのXWikiが連携できない」障害[[project_dokploy_multiserver_plan]]の「Swarm方式の再考」セクション参照と同根の障害で、応急処置`swarm leave`→再`join`)が一時しのぎに過ぎなかったことを意味していた。
### 対応: Remote Servers方式へ本移行
ユーザー承認を得て深夜に一括実施。
1. Node B: `docker swarm leave --force`で完全離脱
2. Node A: `docker node rm dokploy-node-b`でクラスタから古いエントリ削除Node A自体はSwarmを維持——`dokploy`/`dokploy-postgres`がSwarm service依存のため。Node B離脱後は事実上シングルードSwarmとなり、今回の根本原因だったード間raft/overlay同期問題が構造的に起こらなくなった
3. Node B: `docker network create dokploy-network`でbridgeネットワークを作成overlay不使用
4. Node B: `dokploy-traefik`をstandaloneコンテナとして再構築。Node A側の稼働中Traefik`docker inspect`で image/mounts/ports/restart policyを取得をテンプレートに再現
5. Node B: XWiki/xwiki-dbを`docker compose -p xwiki-document-server-38jpgp up -d`でプロジェクト名を明示指定して再作成し、既存ボリューム(データ損失なし)を引き継ぎ
6. mail-infra⇔XWiki間のSMTP疎通は、要件1実装時点で既にホストポート公開+プライベートIP直結`172.26.11.222:587`)で対応済みだったため変更不要、疎通確認のみで完了
7. Keycloak等Node A上のstandaloneコンテナは無停止のまま稼働継続、影響なし
結果: XWiki(HTTP 200)・Keycloak(HTTP 200)・SMTP疎通、全て復旧確認。
**Dokploy Remote Server登録も完了**: Node BはDokploy UI上に元々`dokploy-node-B`として登録済みIP/SSH鍵とも今回作り直した環境でそのまま有効だったため、新規登録は不要で「Setup Server」ボタン実行のみで完結。実行後の検証結果は全項目greenDocker/RClone/Nixpacks/Buildpacks/Railpack Installed、Docker Swarm Initialized、Dokploy Network Created、Main Directory Created、Privilege Mode/Docker Group正常
重要な点: Setup Serverの過程でNode Bは**独自のシングルードSwarm**として初期化された(`Nodes: 1`、ClusterIDはNode Aと別物。これはDokploy Remote Servers方式の標準動作ードが単独でコンテナ管理用にSwarmを使うだけで、ード間クラスタは組まないで、今回の障害原因だった「マルチード間のraft/overlay同期」は構造的に発生しなくなった。手動作成した`dokploy-network`bridgeやXWiki/Traefikコンテナも実行前後で変化なく稼働継続、影響なし。
セキュリティタブでUFW/SSH/Fail2Banの推奨設定不備Password Auth有効、PAM有効、Fail2Ban SSH保護無効などが指摘されたが、今回の障害とは無関係の別課題として次回対応。
詳細な手順・教訓は[[project_dokploy_multiserver_plan]]に記録。
## 追記(2026-08-13): Office文書プレビューをスクロール可能な固定枠に変更
ユーザー指摘「doc/xlsの中身がそのまま表示されてしまっている。プレビューウインドウのようにできないか」。`{{office}}`マクロは変換結果をページ本文に直接インライン展開する仕様のため、文書が長いとページ全体がそれだけ縦に伸びてしまっていたPDFは`{{pdfviewer}}`が最初からiframeで固定サイズ表示のため対象外
XWikiのMacroDescriptor APIから高さ制御パラメータの有無を調べようとしたが`getParametersMap()`が存在せず断念API名不一致、深追いせず方針転換。代わりに`<div style="max-height:500px; overflow-y:auto; ...">{{office}}...{{/office}}</div>`でoffice出力全体を囲む方式を「確定付帯工事」ページで直接検証→正常にスクロール可能な固定枠として機能することを確認。
`import-notepm.js`の`attachmentMacroFor`関数でoffice用マクロ生成部分をこのdiv囲み版に修正し、全1042ページに`--update-only`で再適用、0件エラー。
## 未確定・残課題(2026-08-13時点、最新)
- **XWikiのレスポンスが重い(原因未特定、保留)**: 調査時点ではCPU/メモリともアイドル近く逼迫していなかった。直前のコンテナ再作成でSolr全文検索インデックスの全件再構築(1042ページ+大量PDF/Officeのテキスト抽出、約20分CPU高負荷)が走っていた影響の可能性と、Node Bのメモリがホスト4GB中available 1.1GB程度とカツカツな点の両方が候補。実測TTFBは約0.9秒で致命的な遅さではなかった。ユーザー判断で「問題が再度顕在化してから改めて調査する」方針。次回発生時は`docker stats`/`free -h`で今現在の負荷か、`docker logs`でSolr再構築等の重い処理が走っていないかから確認再開。恒常的にメモリ不足が原因なら根本対策はNode Bのインスタンススケールアップ(コスト増・要ダウンタイム)。詳細は[[project_dokploy_multiserver_plan]]参照
- Dokploy Setup Serverのセキュリティ検証で指摘された不備が未対応: SSH Password Auth有効・PAM有効(鍵認証のみなら両方無効化推奨)、Fail2Ban未インストール(SSH Protection無効)
- Keycloak同期ユーザー(351名)のXWiki事前登録・OIDC自動作成抑制の方針は保留のまま(86名分実装・登録したが、ユーザー指示で全削除済み。実装自体(`apps/xwiki/scripts/provision-oidc-users.js`)は残置)
- 要件4(ユーザー・組織情報のLINEWORKS自動追従)は保留継続、配属先マスタの管理方法が別途決定してから着手
- 要件5の差分同期スクリプト(NotePM API経由)自体の実装は未着手(設計のみ完了、`apps/xwiki/docs`内の設計メモ参照)
- Keycloak/XWikiのAuthentication Security Module(ブルートフォース対策)が無効のまま(ユーザーの意向で「全部完了したら戻す」保留中)
**解消済み(参考、旧リストから):** サブフォルダ構造対応・リスト構造・添付プレビュー・丸括弧リンク不具合は全て対応完了。Docker Swarmのマルチード不安定性はRemote Servers方式への移行で根治(2026-08-13、[[project_dokploy_multiserver_plan]]参照)。