タグ: growi Page 1 of 5

Growiを7.5.7→8.0.0へアップデートしたときの作業メモ

概要

筆者にとっての必須エディタ、Growi。そのv8.0.0が出たので、以下の要件に基づきアップデートを行いました。

リリース情報

実施日: 2026年7月29日
対象環境: Ubuntu 24.04 (非Docker環境 / systemd運用)
その他要件:

itemversion
OSUbuntu 24.04
GROWI7.5.7
node.js24.14.1
npm11.13.0
pnpm11.1.1

必要要件の確認

GROWIの最新リリースにおける変更点(ES7の完全廃止、jemalloc対応、メモリ最適化、ESM移行)に対する自環境の適合状況を確認。

Elasticsearchバージョン確認

ES8/ES9のみに対応しているため、以下を確認。

curl -X GET "http://localhost:9200/" |grep number

9.4.4となっていたので、要件を満たすことは確認しました。

OSメモリ最適化モジュールの確認

ldconfig -p | grep jemalloc

libjemalloc.so.2 (libc6,x86-64) => /lib/x86_64-linux-gnu/libjemalloc.so.2と出たのでモジュールも確認済みです。

ここまでやれば、覚悟を決めてアップデートを行います。

さっくりとしているが面倒な手順

  1. Growi実行スクリプトの修正を行います。
  2. 修正結果が反映されたことを確認します。
  3. 関連サービスの停止を行います。
  4. (推奨)mongodbをまるごとバックアップしておきます。
  5. Growiのアップデートを行います。
  6. アップデートの確認を行います。

Growi実行スクリプトの修正

※筆者は

systemdに登録したサービス起動プログラムがGrowiのルートディレクトリ/home/www-data/growi配下にあるgrowi-start.shを参照して各種環境をキックしています。

※ご自身の環境に合わせ修正を行ってください※

  • 修正前ファイル
#!/bin/bash

# nvmのdefaultエイリアスが指すディレクトリを動的に取得してPATHに追加
# これにより、nvm installでバージョンを上げてもこのスクリプトの書き換えが不要になります
DEFAULT_NODE_VER=$(cat "$NVM_DIR/alias/default")
export PATH="$NVM_DIR/versions/node/$DEFAULT_NODE_VER/bin:$PATH"
GROWI_DIR="/home/www-data/growi"

cd $GROWI_DIR

# 環境変数の設定
export NODE_ENV=production
export AUDIT_LOG_ENABLED=true
export FORCE_WIKI_MODE=private
export MONGO_URI=mongodb://localhost:27017/growi
export ELASTICSEARCH_URI=http://localhost:9200/growi
export REDIS_URI=redis://localhost:6379
export PASSWORD_SEED=(設定済みのパスワード)

# npm run app:server を使用する(npmが内部で正しいパスを見つけてくれます)
# execを使うことでsystemdとの親和性を維持します
exec npm run app:server

このファイルを修正していきますが:バックアップを確実に行います。

  • ファイルバックアップ
sudo cp -pi /home/www-data/growi/growi-start.sh /path/to/backup/directory/growi-start.sh.$(date +%Y%m%d)
  • ファイルバックアップ確認
diff -u /path/to/backup/directory/growi-start.sh.$(date +%Y%m%d) /home/www-data/growi/growi-start.sh

エラー(差分)がなければ両者ともファイルがあり、バックアップが取れています。

バックアップ後、/home/www-data/growi/growi-start.shの編集を行います。(自分の環境に合わせます)

主な変更内容
  • JEMALLOC の有効化: export JEMALLOC_ENABLED=true を追加
  • LD_PRELOAD の明示: export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 を追加
  • MongoDB 接続プールの最適化: MONGO_URI?maxPoolSize=10 パラメータを付与

具体的には以下のような差分が取れていればOKです。

diff -u /path/to/backup/directory/growi-start.sh.$(date +%Y%m%d) /home/www-data/growi/growi-start.sh
# 環境変数の設定
 export NODE_ENV=production
+export JEMALLOC_ENABLED=true
+export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
 export AUDIT_LOG_ENABLED=true
 export FORCE_WIKI_MODE=private
-export MONGO_URI=mongodb://localhost:27017/growi
+export MONGO_URI=mongodb://localhost:27017/growi?maxPoolSize=10
 export ELASTICSEARCH_URI=http://localhost:9200/growi
 export REDIS_URI=redis://localhost:6379

設定ファイルの修正確認

  • Growiサービスの再起動
sudo systemctl restart growi.service

指定した環境変数および jemalloc ライブラリが GROWI プロセス(Node.js)へ正常にロードされていることを確認しました。

  • プロセスの環境変数確認:
    JEMALLOC_ENABLED=true および LD_PRELOAD が正常に適用されていることを確認。
  • メモリマップ / ファイルハンドラ確認:
    lsof および /proc/{PID}/maps コマンドにより、libjemalloc.so.2 が Node.js プロセスに読み込まれていることを確認。

メンテナンスモード有効化

  1. Growiに管理者権限でログインします。
  2. 管理トップ>アプリ設定に進み、「メンテナンスモードを開始する」をクリックします。
  3. トップページに戻り「メンテナンスモード」が表示されていることを確認します。

バックアップ

以下をバックアップします。

  • mongodbの格納データ
cat /etc/mongod.conf |grep dbPath

として、ここのディレクトリ一式を控えます。(筆者環境 /home/mongodb)

このディレクトリを任意の方法でバックアップします。

  • Growiの添付ファイル一式が納められているディレクトリ(ファイルアップロード先をlocalにしている場合のみ)
/growi/root/directory/apps/app/public

(筆者環境 /home/www-data/growi/apps/app/public)ここも念のためバックアップします。

※ 添付ファイルのアップロード先をAWSやAzureなどにしている場合は不要です

  • vpsや仮想ゲストの場合はシステム全体:推奨

スナップショット機能などでシステム全体をバックアップした方が確実で安心です。

ElasticsearchとGrowiの停止

  • Elasticsearchサービス停止
sudo systemctl stop elasticsearch.service
  • Growiサービス停止
sudo systemctl stop growi.service
  • サービス停止確認
systemctl status elasticsearch.service growi.service | grep Active

inactive(dead)を確認します。

作業前バックアップ

  • データディレクトリを丸ごとコピー (-aオプションでパーミッションを維持)
sudo cp -a /var/lib/elasticsearch/ /path/to/backup/dir/elastic_bk.$(date +%Y%m%d)

自分の環境に合わせます。

  • バックアップ確認
sudo ls -l /path/to/backup/dir/elastic_bk.$(date +%Y%m%d)

バックアップした内容があることを確認します。(※管理者権限でないとこのディレクトリを見ることはできません)

growiディレクトリに移動します

cd /home/www-data/growi && pwd

自分の環境に合わせます。(筆者環境/home/www-data/growi)

リリースタグを確認します。

  • リリースタグ取得
sudo git fetch --tags
  • リリースタグ確認
sudo git tag -l

スペースで確認していき、上記リリースサイトと同じバージョンがあることを確認します。

チェックアウトとインストールを行います。

  • 変更を一時的に退避
sudo git stash
  • チェックアウト
sudo git checkout 【バージョン】

リリースタグは再確認しましょう。今回は 2026/07/28にリリースされたv8.0.0を選択しました。

  • pnpm install
sudo pnpm i

※これができるのは、筆者のgrowi実行環境がrootだからです。このパーミッション設定は確実に確認しましょう。

  • ビルド
NODE_OPTIONS="--max-old-space-size=4096" sudo pnpm run app:build

→ ビルド時のメモリ大量使用を抑えるため上限を抑えています。余談ですが、ここは神に祈る作業です。

ElasticsearchとGrowiの再開

  • Elasticsearchサービス開始
sudo systemctl restart elasticsearch.service
  • Growiサービス開始
sudo systemctl restart growi.service
  • サービス開始確認
systemctl status elasticsearch.service growi.service | grep Active

active(running)を確認します。

メンテナンスモード無効化

  1. Growiに管理者権限でログインします。
  2. 管理トップ>アプリ設定に進み、「メンテナンスモードを終了する」をクリックします。
  3. トップページに戻り「メンテナンスモード」が表示されていないことを確認します。

バージョンアップを確認します。

  1. 画面下部にあるバージョンがチェックアウトしたバージョン(v8.0.0)であることを確認します。
  2. 各種機能(ページ閲覧や編集)などが正常に行えるかを確認します。

追加モジュールが動いていることを確認します。

  • 一時的に特権ユーザー昇格

※一般権限で見られないため

sudo su -
  • モジュール追加確認
 pgrep -f "node.*growi" | xargs -I {} lsof -p {} 2>/dev/null | grep jemalloc
  • 実行例
MainThrea 479521 root  mem       REG  253,1    830656    15385 /usr/lib/x86_64-linux-gnu/libjemalloc.so.2

バージョンアップ後の作業

必要に応じてバックアップしたファイル一式やスナップショットを削除します。

Growi 7.5.x→7.5.yへのアップグレード

概要

Growi 7.5.x → Growi 7.5.yにアップデートする 手順です。

7.5.1 → 7.5.2への手順で実際に実施しています。

前提

さっくりとした手順

  1. Growiをメンテナンスモードにします。
  2. Growi・Elasticsearchのサービスを停止します。
  3. バックアップを取ります。
  4. gitコマンドで最新版をcheckoutします。
  5. アップグレードを行います。
  6. Growiのメンテナンスモードを解除します。
  7. アップグレードされたことを確認します。

メンテナンスモード有効化

  1. Growiに管理者権限でログインします。
  2. 管理トップ>アプリ設定に進み、「メンテナンスモードを開始する」をクリックします。
  3. トップページに戻り「メンテナンスモード」が表示されていることを確認します。

バックアップ

以下をバックアップします。

  • mongodbの格納データ
cat /etc/mongod.conf |grep dbPath

として、ここのディレクトリ一式を控えます。(筆者環境 /home/mongodb)

このディレクトリを任意の方法でバックアップします。

  • Growiの添付ファイル一式が納められているディレクトリ(ファイルアップロード先をlocalにしている場合のみ)
/growi/root/directory/apps/app/public

(筆者環境 /home/www-data/growi/apps/app/public)ここも念のためバックアップします。

※ 添付ファイルのアップロード先をAWSやAzureなどにしている場合は不要です

  • vpsや仮想ゲストの場合はシステム全体:推奨

スナップショット機能などでシステム全体をバックアップした方が確実で安心です。

ElasticsearchとGrowiの停止

  • Elasticsearchサービス停止
sudo systemctl stop elasticsearch.service
  • Growiサービス停止
sudo systemctl stop growi.service
  • サービス停止確認
systemctl status elasticsearch.service growi.service | grep Active

inactive(dead)を確認します。

作業前バックアップ

  • データディレクトリを丸ごとコピー (-aオプションでパーミッションを維持)
sudo cp -a /var/lib/elasticsearch/ /path/to/backup/dir/elastic_bk.$(date +%Y%m%d)

自分の環境に合わせます。

  • バックアップ確認
sudo ls -l /path/to/backup/dir/elastic_bk.$(date +%Y%m%d)

バックアップした内容があることを確認します。(※管理者権限でないとこのディレクトリを見ることはできません)

growiディレクトリに移動します

cd /home/www-data/growi && pwd

自分の環境に合わせます。(筆者環境/home/www-data/growi)

リリースタグを確認します。

  • リリースタグ取得
sudo git fetch --tags
  • リリースタグ確認
sudo git tag -l

スペースで確認していき、上記リリースサイトと同じバージョンがあることを確認します。

チェックアウトとインストールを行います。

  • 変更を一時的に退避
sudo git stash
  • チェックアウト
sudo git checkout 【バージョン】

リリースタグは再確認しましょう。今回は 2026/04/23にリリースされたv7.5.2を選択しました。

  • pnpm install
sudo pnpm i
  • ビルド
NODE_OPTIONS="--max-old-space-size=4096" sudo pnpm run app:build

→ ビルド時のメモリ大量使用を抑えるため上限を抑えています。

ElasticsearchとGrowiの再開

  • Elasticsearchサービス開始
sudo systemctl restart elasticsearch.service
  • Growiサービス開始
sudo systemctl restart growi.service
  • サービス開始確認
systemctl status elasticsearch.service growi.service | grep Active

active(running)を確認します。

メンテナンスモード無効化

  1. Growiに管理者権限でログインします。
  2. 管理トップ>アプリ設定に進み、「メンテナンスモードを終了する」をクリックします。
  3. トップページに戻り「メンテナンスモード」が表示されていないことを確認します。

バージョンアップを確認します。

  1. 画面下部にあるバージョンがチェックアウトしたバージョン(v7.5.x)であることを確認します。
  2. 各種機能(ページ閲覧や編集)などが正常に行えるかを確認します。

バージョンアップ後の作業

必要に応じてバックアップしたファイル一式やスナップショットを削除します。

Growi v7.5.0のアップデートメモ(nodeのアップデート含む)

Grwoiをv7.5.0にアップデートしたときのメモです。

アップデート概要

  • 対象バージョン: v7.4.7 → v7.5.0
  • 実行環境:
    • Ubuntu 24.04
    • Apache 2.4によるリバースプロキシー

主要な変更点:

  • Node アップデートによる Next.js v16 / Vite v6 / Turbopack への移行
  • リバースプロキシにy-websocket への変更、
  • RegExp.escape() の採用

前提

  • nvmでnodeのバージョンを管理していること。
  • リバースプロキシの変更手順が整っていること。

事前準備・環境更新

最新のGROWI要件に合わせ、Node.jsランタイムをアップデートしました。

-Node.jsの更新:

nvm を使用して v24.14.1 をインストール。 筆者はroot環境で実施しているため

sudo su -

で管理者権限になってから実施しました。

  • nvm バージョンアップ
nvm install 24

bash
nvm use 24

nvm alias default 24
  • ビルドツールの更新:

はまったポイントです。一度バージョンアップするとv24環境で pnpmが消えてしまうので再有効化します。

corepack enable
corepack prepare pnpm@latest --activate

ビルドプロセスの修正

後は基本的に筆者が行っているものに習います。

growiディレクトリに移動します

cd /home/www-data/growi && pwd

自分の環境に合わせます。(筆者環境/home/www-data/growi)

リリースタグを確認します。

  • リリースタグ取得
sudo git fetch --tags
  • リリースタグ確認
sudo git tag -l

スペースで確認していき、上記リリースサイトと同じバージョンがあることを確認します。

チェックアウトとインストールを行います。

  • 変更を一時的に退避
sudo git stash
  • チェックアウト
sudo git checkout 【バージョン】

リリースタグは再確認しましょう。今回は 2026/04/07にリリースされたv7.5.0を選択しました。

メモリ不足の対処

  • pnpm install 時のセキュリティ警告(sharpのビルド停止)およびメモリ不足対策を実施しました。
  • ネイティブバイナリの許可:
    ```bash
    pnpm approve-builds
リストから sharp を選択して許可

bash
pnpm install

- Turbopackによるビルド:

メモリ消費を抑えるため、上限を指定して実行。

bash
NODE_OPTIONS="--max-old-space-size=4096" pnpm run app:build

### リバースプロキシ (Apache) の修正


同時編集プロトコルが `y-websocket` に変更されたことに伴い、`growi.conf` の書き換えルールを厳密化しました。

修正内容: すべてのWS通信ではなく、`/yjs` パスのみをWSプロキシへ流すよう変更。

apache
# リバースプロキシー設定
RewriteEngine on

# 1. /yjs へのアクセスのみ WebSocket プロキシへ飛ばす
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{REQUEST_URI} ^/yjs [NC]
RewriteRule /(.) ws://localhost:3000/$1 [P,L]

# 2. それ以外の通常の HTTP リクエスト
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
```

起動スクリプトの修正

2度目のはまりポイントです。RegExp.escape is not a function エラー(Node.jsのバージョン不足)を解消するため、サービスが参照する PATH を更新しました。
(これは筆者環境なので)

growi-start.sh の修正:

# 旧: v20.19.2 -> 新: v24.14.1
export PATH="/root/.nvm/versions/node/v24.14.1/bin:$PATH"

Growiの再起動

  • Growiサービス開始
sudo systemctl restart growi.service
  • サービス開始確認
systemctl status growi.service

active(running)を確認します。

完了確認

  • [x] 管理画面「システム情報」にて GROWI 7.5.0 / Node.js 24.14.1 を確認。
  • [x] ページリストの取得エラー(APIエラー)が解消されたことを確認。
  • [x] ページ複製時の RegExp.escape エラーが解消されたことを確認。
  • [x] Elasticsearch v9 への接続および検索ができることを確認。

Growi環境:axiosサプライチェーンの影響確認。

2026年3月31日に発覚したaxiosのサプライチェーン攻撃

内容を聞くだけでゾッとする話だったので、調べつつ筆者がインターネット上に設置しているgrowiサーバでの影響を調べました。

筆者環境

  • Growi v7.4.7
  • npm 11.4.0
  • Apache 2.4によるリバースプロキシー
  • Ubuntu 24.04

自環境の白黒確認

まずは、動いている環境のaxiosバージョンを特定することが最優先。筆者はこのライブラリを恥ずかしながら聞くまで知らなかったのですが 、JavaScriptのデファクトスタンダード的なHTTPクライアントライブラリ と聞いたので「絶対に入っているだろう」の断定で動きました。

調査コマンド

  • Growiのルートディレクトリに移動
cd /path/to/growi

自分の環境に合わせます(筆者環境/home/www-data/growi)

  • インストール済みaxiosの調査 
npm ls axios

または

 cat pnpm-lock.yaml |grep axios
  • 調査結果
axios@1.11.0

axios@0.26.1

axios@0.21.4

判定:安全

影響を受けるバージョンは

  • axios@1.14.1
  • axios@0.30.4

そのため、攻撃対象となったaxiosを利用していない(つまり、攻撃コードは混入していない)ものとなっています。

念のための確認

ほぼないとは思いますが「npm updateを実行してしまったかも」の場合は、今回のケースで攻撃者が埋め込んだマルウェアが生成するディレクトリの有無を調べます。

find node_modules -name "plain-crypto-js"

結果、何もなければ物理的にもクリーンです。

また、筆者は該当vpsでBookStackやsnipe-itなどのLaravelライブラリを動かしていますが

cd /path/to/BookStack && pwd
npm ls axios
BookStack@ /home/www-data/BookStack

└── (empty)

と、いずれも(PHPメインであるためか)動いていない状態が分かりました。

サプライチェーン攻撃の危険さ

開発者が信頼している公式ツールをそのまま使っただけで感染する状態であった点にあります。供給者のツールに攻撃ツールを仕込んでいたということで「サプライチェーン攻撃」だそうで。(発覚後、npm が悪性 axios バージョンを削除)

シーケンス

これをmermaid.jsにまとめたのがこちら。

sequenceDiagram participant Dev as 開発者 / サーバー participant NPM as npm レジストリ participant Mal as 悪意のあるパッケージ<br/>(plain-crypto-js) participant C2 as 攻撃者サーバー<br/>(C2サーバー) Note over Dev, NPM: 1. 汚染された axios@1.14.1 を要求 Dev->>NPM: npm install (axios要求) NPM-->>Dev: axios と依存の plain-crypto-js を配信 Note over Dev: 2. インストール直後にフックが発動 Dev->>Dev: postinstall: node setup.js を自動実行 Note over Dev, C2: 3. OSを判別し、マルウェアを落とし込む Dev->>C2: 難読化解除 + OS情報送信 + 通信開始 C2-->>Dev: 各OS用ペイロード (Mac/Win/Linux) を送信 rect rgb(240, 240, 240) Note right of Dev: Linuxの場合: /tmp/ld.py を実行<br/>Windowsの場合: 隠しPowerShellを実行 end Note over Dev: 4. 証拠隠滅 (セルフデストラクト) Dev->>Dev: setup.js を削除 Dev->>Dev: package.json をクリーンな状態に書き換え Note over Dev: node_modules 内は<br/>一見シロに見える

恐ろしさのポイント

  1. 自動実行: npm install した瞬間に、何も操作せずともウイルス(RAT)が仕込まれます。
  2. OS別の狙撃: Linux、Windows、macOSそれぞれに最適な攻撃コードが送り込まれます。
  3. 証拠隠滅: 実行後に自分自身の痕跡(setup.js)を消去し、package.json を書き換えて「何事もなかったかのように」振る舞います。

今後の対策と教訓

たまたまGrowiのアップデートタイミングとずれていた、そして、 pnpm-lock.yamlがあるおかげで、意図しない汚染版の混入を防ぐことができました。

なお、今回のケースで「黒」だった場合は、該当axiosの除去に留まりません。

VPS乗の全ての認証情報

  • SSH鍵
  • APIトークン

などが漏洩されたものとして作り直す必要があります。

以上、4月1日に公開すべき内容ではないものでした。

Grwoi/Apacheリバースプロキシ、セキュリティ判断FからB+まで改善した記録

昨日の続き。

HTTP ObservatoryでRedmineサイトをB-→B+に改善しましたが、

筆者環境の

  • Apache 2.4によるリバースプロキシ
  • Growi v7.4.7

環境のHTTP診断もそのぐらいであろうと思ったらまさかのF判定を食らいました。

Fランクの内訳

最初の診断結果では、以下の項目で派手に減点を食らっていました。

  • CSP (Content Security Policy):
    • 未設定 (-25)
  • Cookies:
    • Secure属性なし (-20)
  • HSTS: 認識されず
    • (-20)
  • X-Frame-Options / X-Content-Type-Options:
    • 認識されず (-25)

特に謎だったのが、「Apache側でHeader設定を入れているはずなのに、認識されない(Failed)」という点でした。

アプリとApacheの二重出力。

原因を調査するため、curl -I コマンドで生のレスポンスヘッダーを確認しました。

curl -I https://growi-site
Strict-Transport-Security: max-age=63072000
Strict-Transport-Security: max-age=15552000; includeSubDomains  # <-- 2重に出ている
X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff # <-- 2重に出ている
Set-Cookie: connect.sid=...; Path=/; HttpOnly  # <-- Secure属性がない

原因の分析:

リバースプロキシ構成では、「バックエンド(アプリ側)が吐き出すヘッダー」と「Apacheが追加しようとするヘッダー」が両方ブラウザに届いてしまい、重複が発生。

診断ツール側が「どの設定を信頼すべきか判断できない」としてエラーになっていたのです。

これを回避するための手段が以下の通りです。

さっくりとした手順

  1. Apacheの設定ファイルを書き換えます。
  2. 設定を反映します。
  3. 設定反映を確認します。

Apacheの設定ファイルを書き換え

  • 設定ファイルのバックアップ
sudo cp -pi /etc/apache2/sites-available/growi-site.conf /path/to/backup/growi-site.conf.$(date +%Y%m%d)

.confの名前やバックアップディレクトリは自分の環境に合わせます。

  • 設定ファイルのバックアップ確認
diff -u /path/to/backup/growi-site.conf.$(date +%Y%m%d) /etc/apache2/sites-available/growi-site.conf

エラーがないことを確認します。

/etc/apache2/sites-available/growi-site.confを以下のように修正していきます。(要管理者権限)

  • 修正前
    Header always set Strict-Transport-Security "max-age=63072000"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-XSS-Protection "1; mode=block"
  • 修正後
        # 重複を防ぐため、通常のレスポンスとProxyレスポンスの両方から一旦削除
        Header unset Strict-Transport-Security
        Header always unset Strict-Transport-Security
        Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

        Header unset X-Content-Type-Options
        Header always unset X-Content-Type-Options
        Header always set X-Content-Type-Options "nosniff"

        Header unset X-Frame-Options
        Header always unset X-Frame-Options
        Header always set X-Frame-Options "SAMEORIGIN"

        # 足りなかった重要ヘッダーを新規追加
        Header always set Referrer-Policy "strict-origin-when-cross-origin"

        # CSP: アプリの動作を維持しつつ、安全性を高める(object-src 'none' を追加)
        Header always unset Content-Security-Policy
        Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:; object-src 'none';"

        # CookieのSecure属性をApache側で強制付与
        Header edit Set-Cookie ^(.-)$ "$1; Secure; SameSite=Lax"

この「2重出力」を解消し、かつアプリ側で足りない属性を付与するために、Apacheの設定を「既存ヘッダーの完全掃除 + 強制セット」という戦略に変更しました。

  • 修正後の差分確認
diff -u /path/to/backup/growi-site.conf.$(date +%Y%m%d) /etc/apache2/sites-available/growi-site.conf

以下のような差分を確認します。

-    Header always set X-Content-Type-Options "nosniff"
-    Header always set X-Frame-Options "SAMEORIGIN"
-    Header always set X-XSS-Protection "1; mode=block"
+        # 重複を防ぐため、通常のレスポンスとProxyレスポンスの両方から一旦削除
+        Header unset Strict-Transport-Security
+        Header always unset Strict-Transport-Security
+        Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
+
+        Header unset X-Content-Type-Options
+        Header always unset X-Content-Type-Options
+        Header always set X-Content-Type-Options "nosniff"
+
+        Header unset X-Frame-Options
+        Header always unset X-Frame-Options
+        Header always set X-Frame-Options "SAMEORIGIN"
+
+        # 足りなかった重要ヘッダーを新規追加
+        Header always set Referrer-Policy "strict-origin-when-cross-origin"
+        
+        # CSP: アプリの動作を維持しつつ、安全性を高める(object-src 'none' を追加)
+        Header always unset Content-Security-Policy
+        Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:; object-src 'none';"
+
+        # CookieのSecure属性をApache側で強制付与
+        Header edit Set-Cookie ^(.-)$ "$1; Secure; SameSite=Lax"

設定反映を確認します。

  • 構文確認
sudo apache2ctl configtest

Syntax OKを確認します。

  • サービス再起動
sudo systemctl restart apache2.service
  • サービス再起動確認
systemctl status apache2.service

active (running)を確認します。

設定反映の確認

curl -I https://growi-site
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:; object-src 'none';

など、ヘッダーのダブりがないことを確認します。

HTTP Observe に再度アクセスし、セキュリティ診断を行いました。

再スキャンの結果、主要な項目はすべて Passed となり、無事に B+ を獲得。

  • HSTS / XFO / NoSniff: 重複が解消され、正しく認識された。
  • Cookies: Secure/SameSite属性が付き、満点。
  • Referrer Policy: 合格。

なぜ「A+」ではないのか?

CSPにおける 'unsafe-inline''unsafe-eval' が「安全ではない」として減点対象になっています。しかし、これらを外すとモダンなWebアプリ(Wikiのエディタなど)は動かなくなることが多いため、利便性とのトレードオフとして 「B+」は現実的な落とし所と言えます。

まとめ

  1. 診断ツールで「Recognizedできない」と言われたら、まずは curl -I で重複を疑う。
  2. Apache側で Header always unset してから set することで、バックエンドとの競合を確実に排除できる。
  3. CookieのSecure化もApacheの Header edit で一発解決。

GROWIスタックの運用を効率化するBashスクリプトの作成

1. はじめに

GROWI(Wikiシステム)をセルフホストする場合、本体の他にMongoDB、Elasticsearch、そしてリバースプロキシとしてのApacheなど、複数のサービスを管理する必要があります。これらを個別に手動で起動・停止するのは手間がかかり、手順ミスも起きやすくなります。

そこで、サービス間の依存関係を考慮し、安全かつ簡潔に管理するためのBashスクリプトを作成しました。

1-1. 筆者環境

  • Ubuntu 24.04
  • Growi (systemdにサービス登録済み)
  • Apacheのバーチャルホストでリバースプロキシ運用
  • mongodb
  • elasticsearch

2. スクリプトの主な特徴

  • 依存関係を考慮した起動順序:
    • DB → 検索エンジン(起動待ち含む) → Apache設定 → GROWI本体の順で確実に立ち上げます。
  • Apache設定の動的切り替え:
    • a2ensite / a2dissite を活用し、サービス停止時にはApache自体は止めず、GROWIへのアクセスパスのみをクリーンに遮断します。
  • 対話モードとコマンドライン引数の両対応:
    • 単体実行ではメニューが表示され、引数を渡せば自動実行が可能です。
  • エラーハンドリング: 各ステップで実行状態をチェックし、異常があれば即座に中断・通知します。

3. 管理スクリプト: manage-growi.sh

systemctlをいじるので、root権限で作っておきます。

#!/bin/bash

# ==============================================================================
# GROWIスタック管理スクリプト
# 対応サービス: MongoDB, Elasticsearch, Apache2, GROWI
# ==============================================================================

# --- [ 設定セクション ] ---
APACHE_CONF_NAME="growi.conf"
SITE_NAME="${APACHE_CONF_NAME%.conf}"
SERVICES=("mongod.service" "elasticsearch.service" "apache2.service" "growi.service")

# カラー出力設定
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m'

# --- [ ユーティリティ関数 ] ---

log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_err()  { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
log_ok()   { echo -e "${GREEN}[SUCCESS]${NC} $1"; }

# 実行権限チェック
if [[ $EUID -ne 0 ]]; then
   log_err "このスクリプトの実行にはroot権限(sudo)が必要です。"
fi

# サービスの状態確認
is_active() {
    systemctl is-active --quiet "$1"
    return $?
}

# --- [ メイン機能 ] ---

start_services() {
    log_info "GROWIスタックの起動を開始します。"

    # 1. MongoDB
    log_info "データベース (MongoDB) を起動中..."
    systemctl start mongod.service
    is_active mongod.service || log_err "MongoDBの起動に失敗しました。"

    # 2. Elasticsearch
    log_info "検索エンジン (Elasticsearch) を起動中..."
    systemctl start elasticsearch.service
    # 応答待ち(最大75秒)
    for i in {1..15}; do
        if is_active elasticsearch.service; then break; fi
        [[ $i -eq 15 ]] && log_err "Elasticsearchの起動がタイムアウトしました。"
        sleep 5
    done

    # 3. Apache (設定の有効化)
    log_info "Apacheのリバースプロキシ設定を有効化しています..."
    if [ ! -L "/etc/apache2/sites-enabled/${APACHE_CONF_NAME}" ]; then
        a2ensite "${SITE_NAME}" > /dev/null
    fi
    systemctl reload apache2.service || systemctl start apache2.service

    # 4. GROWI 本体
    log_info "GROWI本体を起動中..."
    systemctl start growi.service
    sleep 3
    if is_active growi.service; then
        log_ok "すべてのサービスが正常に起動しました。"
    else
        log_err "GROWIの起動に失敗しました。ログを確認してください。"
    fi
}

stop_services() {
    log_info "GROWIスタックを停止します。"

    # サービス影響を最小限にするため、リバースプロキシから先に遮断
    log_info "GROWI本体を停止中..."
    systemctl stop growi.service

    log_info "Apacheの設定を無効化しています..."
    if [ -L "/etc/apache2/sites-enabled/${APACHE_CONF_NAME}" ]; then
        a2dissite "${SITE_NAME}" > /dev/null
        systemctl reload apache2.service
    fi

    log_info "バックエンドサービスを停止中..."
    systemctl stop elasticsearch.service
    systemctl stop mongod.service

    log_ok "すべての関連サービスが正常に停止しました。"
}

show_status() {
    echo -e "\n${YELLOW}--- サービス稼働状況 ---${NC}"
    printf "%-25s %s\n" "サービス名" "ステータス"
    printf "%-25s %s\n" "------------------------" "-------"
    for s in "${SERVICES[@]}"; do
        if is_active "$s"; then
            printf "%-25s ${GREEN}RUNNING${NC}\n" "$s"
        else
            printf "%-25s ${RED}STOPPED${NC}\n" "$s"
        fi
    done

    if [ -L "/etc/apache2/sites-enabled/${APACHE_CONF_NAME}" ]; then
        echo -e "Apache設定 (${APACHE_CONF_NAME}): ${GREEN}ENABLED${NC}"
    else
        echo -e "Apache設定 (${APACHE_CONF_NAME}): ${RED}DISABLED${NC}"
    fi
    echo ""
}

# --- [ エントリポイント ] ---
ACTION=${1:-"menu"}

case "$ACTION" in
    start)   start_services ;;
    stop)    stop_services ;;
    status)  show_status ;;
    restart) stop_services; start_services ;;
    menu)
        echo -e "${YELLOW}実行する操作を選択してください:${NC}"
        select opt in "Start" "Status" "Stop" "Restart" "Exit"; do
            case $opt in
                Start) start_services; break ;;
                Status) show_status; break ;;
                Stop) stop_services; break ;;
                Restart) stop_services; start_services; break ;;
                Exit) exit 0 ;;
                *) echo "無効な選択です。" ;;
            esac
        done
        ;;
    *)
        echo "Usage: $0 {start|stop|status|restart}"
        exit 1
        ;;
esac

4. 利用前の事前準備

スクリプトを使用するには、実行権限の付与と、システム上のファイル名の確認が必要です。

  1. 保存と実行権限の付与

ファイルを manage-growi.sh として保存した場合、以下のコマンドを実行します。

sudo chmod +x manage-growi.sh
  1. 設定の確認(重要)

スクリプト内の APACHE_CONF_NAME="growi.conf" が、実際に /etc/apache2/sites-available/ に存在するファイル名と一致しているか確認してください。

5.使い方(コマンド一覧)

このスクリプトは root権限(sudo) が必要です。

A. 対話型メニューで操作する

引数なしで実行すると、メニューが表示されます。

sudo ./growi-stack.sh

B. 直接コマンドを指定する

特定の操作を即座に実行したい場合に使用します。

  • 起動: sudo ./growi-stack.sh start
  • 停止: sudo ./growi-stack.sh stop
  • 状態確認: sudo ./growi-stack.sh status
  • 再起動: sudo ./growi-stack.sh restart

6. 表示・実行例

ステータス確認時の表示 (status)

現在の各サービスの稼働状況が一覧で表示されます。

--- サービス稼働状況 ---
サービス名                  ステータス
------------------------   -------
mongod.service             RUNNING
elasticsearch.service      RUNNING
apache2.service            RUNNING
growi.service              RUNNING
Apache設定 (growi.conf): ENABLED

起動処理の実行例 (start)

依存関係を考慮し、DBから順に起動していきます。

[INFO] GROWIスタックの起動を開始します。
[INFO] データベース (MongoDB) を起動中...
[INFO] 検索エンジン (Elasticsearch) を起動中...
[INFO] Apacheのリバースプロキシ設定を有効化しています...
[INFO] GROWI本体を起動中...
[SUCCESS] すべてのサービスが正常に起動しました。

7. スクリプトの主な特徴と処理順序

このスクリプトは、単なる一括起動ではなく、GROWIの依存関係を考慮した設計になっています。

順序起動時 (Start)停止時 (Stop)理由
1MongoDBGROWI本体DBがないとアプリが落ちるため / 停止は入り口から。
2ElasticsearchApache設定無効検索エンジンを準備。
3Apache設定有効Elasticsearchインフラが整ってから外部公開。
4GROWI本体MongoDB最後にアプリを立ち上げる。

8. 運用のヒント

  • トラブルシューティング: もし [ERROR] が出た場合は、個別のログを確認してください。
  • GROWIのログ: journalctl -u growi.service -f
  • Elasticsearchのログ: journalctl -u elasticsearch.service -f
  • 自動起動設定: サーバー再起動時に自動で立ち上げたい場合は、このスクリプトを使わず systemctl enable [サービス名] を各サービスに設定するのが一般的です。このスクリプトは、メンテナンス時の手動メンテナンス用として最適です。

Growiのsystemdと起動スクリプトの修正。

以下の環境でGrowiを利用。

  • Growi v7.4.1
  • node v20.10.2
  • Ubuntu 24.04
  • Growi実行環境 /home/www-data/growi
  • Growi実行ユーザ:root

v7.4.1で以下の問題点にぶつかったため、growiのスタートアップスクリプトとsystemdで対処したときのメモです。

問題点

  • daemon-reload の遅延: 設定反映に約5分を要していました。
  • 起動プロセスの停滞: サービス開始から実際にアクセス可能になるまで約6分かかっていました。(以前は数秒)
  • 不安定な運用: 異常終了時の自動再起動設定がなく、ログも標準出力のみで追跡が困難でした。

旧設定

  • /etc/systemd/system/growi.service
[Unit]
Description = growi
After=network-online.target mongod.service
After=network.target elasticsearch.service
ConditionPathExists=/home/www-data/growi

[Service]
ExecStart=/home/www-data/growi/growi-start.sh
Restart=no
Type=simple

[Install]
WantedBy=multi-user.target
  • /home/www-data/growi/growi-start.sh
#!/bin/bash

# NVM environmentをロード (NVM_DIRを直接指定)
export NVM_DIR="/root/.nvm" # $HOMEの代わりに直接パスを指定
if [ -s "$NVM_DIR/nvm.sh" ]; then
  \. "$NVM_DIR/nvm.sh"  # nvmをロード
  # 次の行でスクリプト実行時のnodeとnpmのバージョンをログに出力
  echo "NVM for GROWI startup script loaded. Using Node version: $(node -v), npm version: $(npm -v)" > /tmp/growi_nvm_load.log
else
  # NVMが見つからない場合もログに出力
  echo "NVM_DIR ($NVM_DIR) not found or nvm.sh not found for GROWI startup script." > /tmp/growi_nvm_load.log
fi

cd /home/www-data/growi
NODE_ENV=production \
AUDIT_LOG_ENABLED=true \
FORCE_WIKI_MODE=private \
MONGO_URI=mongodb://localhost:27017/growi \
ELASTICSEARCH_URI=http://localhost:9200/growi \
REDIS_URI=redis://localhost:6379 \
PASSWORD_SEED=password \
npm run app:server

原因分析

以下、分析はGemini。

  1. systemdの過負荷: ConditionPathExists が大規模なディレクトリ(growi)をチェックする際、OSレベルでスキャン待ちが発生していた可能性。
  2. NVMの初期化コスト: 起動のたびに nvm.sh を読み込んでいた。これは数百行のシェルスクリプトを実行する処理であり、本番環境のサービス起動としては非常に重い。
  3. プロセスの二重管理: シェルスクリプトが npm プロセスを「子プロセス」として抱えていたため、systemdからの制御効率が悪かった。

何が問題だったのか(ボトルネックの正体)

今回の事象で最大の問題は、「本番環境のサービス起動に、開発環境のような動的な初期化プロセスを組み込んでいたこと」にありました。

具体的には、以下の3つの「待ち」が連鎖していました。

  1. システムチェックによる停滞 (ConditionPathExists) systemdのユニットファイルでGROWIのインストールディレクトリをチェックしていましたが、node_modules を含む膨大なファイル群をOSレベルでスキャンしに行った際、I/O待ちやカーネルレベルのオーバーヘッドが発生し、daemon-reload や起動そのものを著しく遅延させていました。
  2. シェルスクリプトによる二重起動のオーバーヘッド 起動のたびに nvm.sh をロード(source)し、Node.jsのバージョン判定を動的に行っていました。これは開発時には便利ですが、本番サービスとしては数百行のシェルスクリプトを毎回実行することになり、CPUリソースと時間を無駄に消費していました。
  3. プロセスの「親子関係」の不備 systemdから見ると、管理対象が「GROWI本体」ではなく「起動用のシェルスクリプト」になっていました。このため、GROWIが内部でハングアップしてもsystemdが検知できず、再起動もかからないという「運用上の死角」が生まれていました。

これを是正した設定ファイル

設定の前に!

  • 設定ファイルのバックアップ
sudo cp -pi /etc/systemd/system/growi.service /path/to/backup/growi.service.$(date +%Y%m%d)
sudo cp -pi /home/www-data/growi/growi-start.sh /path/to/backup/growi-start.sh.$(date +%Y%m%d)
  • diffによるバックアップ確認
sudo diff -u /path/to/backup/growi.service.$(date +%Y%m%d) /etc/systemd/system/growi.service 
sudo diff -u /path/to/backup/growi-start.sh.$(date +%Y%m%d) /home/www-data/growi/growi-start.sh

新しいファイル本体

  • /etc/systemd/system/growi.service
[Unit]
Description=GROWI Service
After=network-online.target mongod.service elasticsearch.service redis.service
Wants=network-online.target

[Service]
Type=simple
User=root
Group=root
WorkingDirectory=/home/www-data/growi
ExecStart=/bin/bash /home/www-data/growi/growi-start.sh
Restart=always
RestartSec=10
StandardOutput=append:/var/log/growi.log
StandardError=append:/var/log/growi-error.log

[Install]
WantedBy=multi-user.target
  • /home/www-data/growi/growi-start.sh
#!/bin/bash

# Node.jsバイナリへのパスを直接追加 (nvm.shのロードを回避して高速化)
export PATH="/root/.nvm/versions/node/v20.19.2/bin:$PATH"
GROWI_DIR="/home/www-data/growi"

cd $GROWI_DIR

# 環境変数の設定
export NODE_ENV=production
export AUDIT_LOG_ENABLED=true
export FORCE_WIKI_MODE=private
export MONGO_URI=mongodb://localhost:27017/growi
export ELASTICSEARCH_URI=http://localhost:9200/growi
export REDIS_URI=redis://localhost:6379
export PASSWORD_SEED=password

# execにより、このシェル自体をnpmプロセスに切り替える
exec npm run app:server

※このpasswordは、旧設定をそのまま利用します。でない場合、「Growiにログインできない」という地獄が待っています。

ファイル差し替え後の挙動

  • systemdリロード
sudo systemctl daemon-reload
  • growi再起動
sudo systemctl restart growi.service
  • growi再起動確認
systemctl status growi.service

active(running) を確認します。

その後、

  1. growiが起動する
  2. 新しいセッション(ゲストセッション)で管理者アカウントにログインできる
  3. 一通りの操作 (Wikiページの作成や編集)が行えればOKです。

設定の比較

■ systemd ユニットファイル (growi.service)

項目旧設定 (遅延の原因)新設定 (最適化済)
依存関係Afterが分散、Redisの指定なしAfter/WantsにRedis含め統合
パスチェックConditionPathExists (5分停滞の疑い)削除(高速化に寄与)
実行ユーザ指定なし (デフォルト)User=root / Group=root 明示
作業ディレクトリスクリプト内で cdWorkingDirectory で定義
再起動設定Restart=no (手動復旧が必要)Restart=always (10秒後に自動復旧)
ログ管理標準出力のみ (systemdログに混在)/var/log/growi.log に直接出力

■ 起動スクリプト (growi-start.sh)

項目旧設定 (遅延の原因)新設定 (最適化済)
Node環境構築source nvm.sh (数秒〜数十秒のロス)PATH を直接追加 (0秒)
環境変数\(バックスラッシュ)連結 (ミスしやすい)export 方式 (確実で読みやすい)
実行コマンドnpm run app:server (子プロセスとして実行)exec npm... (プロセスを置き換え)

4. 対処方法のポイント

  • 「動的な環境構築」から「静的なパス指定」へ: 本番サーバでは nvm を毎回読み込む必要はありません。パスを直接通すことが最速の解決策でした。
  • systemdの責務を明確にする: ディレクトリの存在チェックやパス移動はスクリプトではなく、ユニットファイルの WorkingDirectory 等に任せることで、systemdの管理サイクルが正常化しました。
  • プロセスの直結 (exec): OS (systemd) -> Bash -> npm となっていた階層を、exec で OS (systemd) -> npm に直結させたことで、シグナルの伝達やメモリ効率が改善しました。

今後のメンテナンス

Node.jsのバージョンを変更した際のみ、growi-start.sh 内の v20.19.2 というパス文字列を書き換えるだけで対応可能です。

Growi v7.1.x/v7.2.x→v7.4.0以降へのアップデート

概要

Growi 7.2.x → Growi 7.4.0にアップデートする 手順です。

Growi7.3.3より前のバージョンは脆弱性が存在します。Growiをインターネットで公開している方は、こちらの手順で上げましょう。

注意点

  • Growi 7.4.xはElasticSearchがv9でなければ動きません。
  • また、mongodbの最新版は、古いCPUでは動きません。
  • 上記理由のためGrowiをインターネット環境で動かしている場合は以下を十分検討ください。
    • WAFなどで防御する。
    • ハードウェア環境を最新のmongodb / Elasticsearchが動くものへとアップグレードする。
    • インターネット公開を諦める。

前提

  • 既にgrowi v7.1.x/v7.2.xをインストールしていること。
  • 管理画面トップやトップページ右下からバージョンが7.1.xまたは7.2.xであることを再確認します。
  • systemdによってサービス化されていること。
  • 具体的な手順はhttps://atelier.reisalin.com/projects/zettel/knowledgebase/articles/105
  • 最新版や安定版がリリースされていることを以下のサイトで確認していること。
  • https://github.com/growilabs/growi/releases
  • ※設定ファイルの変更やパッケージインストールの変更、nodeのバージョンアップの必要等があれば、それも事前に済ませます。

さっくりはならない手順

  1. Growiをメンテナンスモードにします。
  2. Growi・Elasticsearchのサービスを停止します。
  3. バックアップを取ります。
  4. gitコマンドで最新版をcheckoutします。
  5. アップグレードを行います。
  6. Elasticsearch・Growiのサービスを再開します。
  7. Growiのメンテナンスモードを解除します。
  8. アップグレードされたことを確認します。

メンテナンスモード有効化

  1. Growiに管理者権限でログインします。
  2. 管理トップ>アプリ設定に進み、「メンテナンスモードを開始する」をクリックします。
  3. トップページに戻り「メンテナンスモード」が表示されていることを確認します。

バックアップ

以下をバックアップします。

  • mongodbの格納データ
cat /etc/mongod.conf |grep dbPath

として、ここのディレクトリ一式を控えます。(筆者環境 /home/mongodb)

このディレクトリを任意の方法でバックアップします。

  • Growiの添付ファイル一式が納められているディレクトリ(ファイルアップロード先をlocalにしている場合のみ)
/growi/root/directory/apps/app/public

(筆者環境 /home/www-data/growi/apps/app/public)ここも念のためバックアップします。

※ 添付ファイルのアップロード先をAWSやAzureなどにしている場合は不要です

  • vpsや仮想ゲストの場合はシステム全体:推奨

スナップショット機能などでシステム全体をバックアップした方が確実で安心です。

ElasticsearchとGrowiの停止

  • Elasticsearchサービス停止
sudo systemctl stop elasticsearch.service
  • サービス停止確認
systemctl status elasticsearch.service

inactive(dead)を確認します。

  • Growiサービス停止
sudo systemctl stop growi.service
  • サービス停止確認
systemctl status growi.service

inactive(dead)を確認します。

作業前バックアップ

  • データディレクトリを丸ごとコピー (-aオプションでパーミッションを維持)
sudo cp -a /var/lib/elasticsearch/ /path/to/backup/dir/elastic_bk.$(date +%Y%m%d)

自分の環境に合わせます。

  • バックアップ確認
sudo ls -l /path/to/backup/dir/elastic_bk.$(date +%Y%m%d)

バックアップした内容があることを確認します。(※管理者権限でないとこのディレクトリを見ることはできません)

リポジトリ設定ファイル名をv9用に変更

Elasticsearchのバージョンを指定するリポジトリをv9に変更します。

  • 現行のリポジトリリストをバックアップ
sudo cp -pi /etc/apt/sources.list.d/elastic-8.x.list /path/to/backup/dir/elastic-8.x.list.$(date +%Y%m%d)
  • リポジトリリストのバックアップ確認
diff -u /path/to/backup/dir/elastic-8.x.list.$(date +%Y%m%d) /etc/apt/sources.list.d/elastic-8.x.list
  • リポジトリリストの名前変更
sudo mv /etc/apt/sources.list.d/elastic-8.x.list /etc/apt/sources.list.d/elastic-9.x.list
  • リポジトリリストの名前変更確認
ls -l /etc/apt/sources.list.d/elastic-9.x.list

ファイルがあることを確認します。

sedコマンドでファイル内の参照先を8.xから9.xに書き換え

sudo sed -i 's/8.x/9.x/g' /etc/apt/sources.list.d/elastic-9.x.list

Elasticsearchのアップグレード

  • パッケージ全体のバックアップ
sudo aptitude update

好みでaptitudeを用いています。必要に応じてaptを用いてください。

  • Elasticsearchのアップグレード
sudo aptitude upgrade elasticsearch

※ Growiインストール時、/etc/elasticsearch/jvm.optionsファイルなどの設定変更を行っているため、アップグレード時の設定ファイルを残すかどうかの確認では、必ずN(残す)を選択します。

  • プラグインのアンインストール

Growiに必要なElasticsearchのプラグインは自動更新されません。この処置を執らないとせっかくアップグレードしたのに起動しないという事態が発生します。

sudo /usr/share/elasticsearch/bin/elasticsearch-plugin remove analysis-icu
sudo /usr/share/elasticsearch/bin/elasticsearch-plugin remove analysis-kuromoji
  • プラグインの再インストール
sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-icu
sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji

growiディレクトリに移動します

cd /home/www-data/growi && pwd

自分の環境に合わせます。(筆者環境/home/www-data/growi)

リリースタグを確認します。

  • リリースタグ取得
sudo git fetch --tags
  • リリースタグ確認
sudo git tag -l

スペースで確認していき、上記リリースサイトと同じバージョンがあることを確認します。

チェックアウトとインストールを行います。

  • 変更を一時的に退避
sudo git stash
  • チェックアウト
sudo git checkout 【バージョン】

リリースタグは再確認しましょう。今回は 2025/12/24 リリースされたv7.4.0を選択しました。

  • pnpm install
sudo pnpm i
  • ビルド
sudo npm run app:build

ElasticsearchとGrowiの再開

  • Elasticsearchサービス開始
sudo systemctl restart elasticsearch.service
  • サービス開始確認
systemctl status elasticsearch.service

active(running)を確認します。

  • バージョンアップ確認
curl -X GET "localhost:9200"

"number" : "9.1.3",など、9系にアップグレードされていることを確認します。

  • Growiサービス開始
sudo systemctl restart growi.service
  • サービス開始確認
systemctl status growi.service

active(running)を確認します。

メンテナンスモード無効化

  1. Growiに管理者権限でログインします。
  2. 管理トップ>アプリ設定に進み、「メンテナンスモードを終了する」をクリックします。
  3. トップページに戻り「メンテナンスモード」が表示されていないことを確認します。

バージョンアップを確認します。

  1. 画面下部にあるバージョンがチェックアウトしたバージョン(v7.4.x)であることを確認します。
  2. 各種機能(ページ閲覧や編集)などが正常に行えるかを確認します。

バージョンアップ後の作業

必要に応じてバックアップしたファイル一式やスナップショットを削除します。

Growi アップデートのエラーに対応。(node_module再インストール)

概要

Growiをアップデートをする中でエラーが発生したので対処した時のメモです。

環境

  • Growi v7.3.2 → v7.3.3にアップデートする際の出来事
    • こちらの手順でv7.3.xにアップデート済み
    • Growiの実行ユーザはroot
  • node v20.19.2
  • npm 11.4.0
  • Apacheによるリバースプロキシ

どの段階でエラーが発生したか

sudo git checkoutsudo pnpm isudo npm run app:buildを実行時に

Tasks:    0 successful, 7 total
Cached:    0 cached, 7 tota
  Time:    25.08s 
Failed:    @growi/pdf-converter#gen:swagger-spec
 ERROR  run failed: command  exited (1) 

が出たのでappのビルドが通りませんでした。

エラーの原因

@growi/pdf-converterが利用する@tsed/openapi-utilsというパッケージが、@tsed/diという別のパッケージからcontextという機能をインポートしようとしていますが、インストールされている @tsed/di のバージョンにその機能が存在しないため、SyntaxError となっています。

これは、pnpm のキャッシュや node_modules ディレクトリの状態が不整合になっている場合に発生するということを突き止めました。

もっと有り体に言うと、筆者環境はGrowiはv7.2.x→v7.3.xと順調にアップデートを重ねていったので、パッケージの混在が発生。言うなれば、複数の設計図が混じっていたので(依存関係の不適合)、建設を担当するnpmが「設計図と実装が矛盾している」としてエラーを吐いた次第です。

大前提

Growiプロセス停止確認(systemdに登録している場合)

systemctl status growi.service

で、growiが完全に停止していることを確認してください。

さっくりとした対処

  1. 依存関係をクリーンにします。
  2. 混在しているパッケージを退避します。
  3. パッケージを再インストールします。
  4. Growiのビルドを改めて実行します。

依存関係をクリアする

  • Growiルートディレクトリに移動&確認 (特に重要な作業)

※調査中に別のディレクトリに遷移しているということは極めて多く発生します。特にエラーの対処となればなおさらです。改めて、作業の現在地を確認します。

cd /growi/root/directroy && pwd

自分の環境に合わせます。(筆者環境/home/www-data/growi)

  • キャッシュクリア
sudo pnpm store prune

node_modulesディレクトリの退避

  • ディレクトリ退避前確認
ls -ld node_modules

ディレクトリがあることを確認します。

sudo mv node_modules /path/to/backup/directory/node_modules.$(date +%Y%m%d)

任意のバックアップディレクトリを退避。$(date +%Y%m%d)変数をつけることで現在の日付を付与します。

  • ディレクトリ退避確認
ls -ld node_modules

ディレクトリがないことを確認します。

  • 退避ディレクトリ確認
ls -ld /path/to/backup/directory/node_modules.$(date +%Y%m%d)

退避ディレクトリがあることを確認します。

node_modules再配置

  • 再配置
sudo pnpm install

再びnode_modulesが配置されます。

  • 再配置確認
ls -ld node_modules

ディレクトリがあることを確認します。

  • ビルド
sudo npm run app:build

Growiの再開

  • Growiサービス開始
sudo systemctl restart growi.service
  • サービス開始確認
systemctl status growi.service

active(running)を確認します。サービスが完全に上がっていないとブラウザでGrowiにアクセスしても503エラーが出ます。しばらく待ちましょう。

アップデート確認

Growiが最新バージョンにアップデートされていれば対処完了です。

Growiのフォントをサーバ内のフリーフォントに変更。

Growiの表示フォントを、サーバー内に配置したカスタムフォント(kiloji)に変更するメモを記します。

BookStackと同じような方法でいけるかと思いましたが、Apacheの追加設定が必要でした。

環境(作業の前提条件)

  • Growi v7.3.0
    • Growiの実行ユーザはroot
  • Ubuntu 24.04
  • Apache 2.4によるリバースプロキシ
    • 結果的に、これが解決策でした。

さっくりとした手順

  1. フォントの配置(フォントはこちらを参考に)
  2. Webサーバの設定変更
  3. カスタムCSSの追加
  4. フォント変更確認

フォントファイルの配置

Webブラウザからアクセスできる公開ディレクトリに、使用したいフォントファイルを設置します。上記リンク先に示した「kiloji」を使います。

  • ディレクトリ移動
cd /path/to/growi/packages/preset-themes/public

/path/to/growiは自分の環境に合わせます。(筆者環境/home/www-data/growi)

  • フォント格納ディレクトリ作成
sudo mkdir -p ./fonts/kiloji
  • フォント格納

上記ディレクトリ(筆者例では/home/www-data/growi/fonts/kiloji)

にフォント一式を格納します。

Webサーバー(Apache)の設定ファイルを変更

この設定がハマった部分です。というのも、Growiの使用上、

https://【growi】/fonts/kiloji.wof

などにアクセスしても、アプリはURLとして解釈してしまうからです。

そのため、リバースプロキシで運用しているApacheのconfファイル/etc/apache2/sites-available/growi.confなどの修正を行います。

  • ファイルバックアップ
sudo cp -pi /etc/apache2/sites-available/growi.conf /path/to/backup/directory/growi.conf.$(date +%Y%m%d)
  • ファイルバックアップ確認
diff -u /path/to/backup/directory/growi.conf.$(date +%Y%m%d) /etc/apache2/sites-available/growi.conf

→ 差分がなければ(エラーが表示されなければ)バックアップ成功です。

  • ファイル編集

※編集前

FileETag None 

<FilesMatch "\.(js|css|png|jpg|gif|svg|woff2?)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>

# リバースプロキシー設定
RewriteEngine on
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule /(.*) ws://localhost:3000/$1 [P,L]

ProxyPass        / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/

※編集後

これは、リバーズプロキシ設定の直情に記載します。

# (1) 静的ファイル(フォントなど)のキャッシュ設定
FileETag None
<FilesMatch "\.(js|css|png|jpg|gif|svg|woff2?)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>

# (2) /fonts/ ディレクトリの場所をApacheに教える【重要:この部分を追加してください】
Alias "/fonts/" "/home/www-data/growi/packages/preset-themes/public/fonts/"
<Directory "/home/www-data/growi/packages/preset-themes/public/fonts/">
    Require all granted
</Directory>

# (3) /fonts/ へのアクセスはプロキシの対象から除外する
ProxyPass "/fonts/" "!"

※差分(以下のコマンドを発行して差分を確認)

-FileETag None 
-
+# (1) 静的ファイル(フォントなど)のキャッシュ設定
+FileETag None
 <FilesMatch "\.(js|css|png|jpg|gif|svg|woff2?)$">
     Header set Cache-Control "public, max-age=31536000, immutable"
 </FilesMatch>

+
+# (2) /fonts/ ディレクトリの場所をApacheに教える【重要:この部分を追加してください】
+Alias "/fonts/" "/home/www-data/growi/packages/preset-themes/public/fonts/"
+<Directory "/home/www-data/growi/packages/preset-themes/public/fonts/">
+    Require all granted
+</Directory>
+
+
+# (3) /fonts/ へのアクセスはプロキシの対象から除外する
+ProxyPass "/fonts/" "!"
+
 # リバースプロキシー設定
 RewriteEngine on
 RewriteCond %{HTTP:Upgrade} websocket [NC]
diff -u /path/to/backup/directory/growi.conf.$(date +%Y%m%d) /etc/apache2/sites-available/growi.conf

設定反映を確認します。

  • 構文確認
sudo apache2ctl configtest

Syntax OKを確認します。

  • サービス再起動
sudo systemctl restart apache2.service
  • サービス再起動確認
systemctl status apache2.service

active (running)を確認します。

GrowiのカスタムCSS設定

  1. Growiに管理者としてログインします。
  2. 「管理」>「カスタマイズ」を開きます。
  3. 「カスタムCSS」のテキストボックスに、以下のCSSコードを貼り付ける。 /* --- フォントの定義 --- */ @font-face { font-family: 'kiloji'; font-style: normal; font-weight: normal; src: url('/fonts/kiloji/kiloji.woff2') format('woff2'), url('/fonts/kiloji/kiloji.woff') format('woff'), url('/fonts/kiloji/kiloji.ttf') format('truetype'); } @font-face { font-family: 'kiloji'; font-style: normal; font-weight: bold; src: url('/fonts/kiloji/kiloji_b.woff2') format('woff2'), url('/fonts/kiloji/kiloji_b.woff') format('woff'), url('/fonts/kiloji/kiloji_b.ttf') format('truetype'); } /* --- フォントの適用 (アイコンを壊さない修正版) --- */ body, .growi, .page-main, .wiki, .sidebar-content, h1, h2, h3, h4, h5, h6, p, li, .btn, a, input, textarea { font-family: 'kiloji', sans-serif !important; } /* 基本の文字サイズを改めて指定 */ body { font-size: 16px; }
  4. ページ下部の更新ボタンを押し、設定を保存します。
  5. ブラウザでGrowiのページを開き、キャッシュをクリアして再読み込み(Ctrl + Shift + R)し、フォントが変わっていることを確認できれば作業完了です。

まとめ

今回、この手順がうまくいったのはひとえに「Apacheによるリバースプロキシ化」でした。

http://【growiサイト】:3000

のように、直接(Growiのような)nodeアプリに接続していたら、フォントファイルを透過させるかのようにアクセスさせることは難しかったでしょう。

Page 1 of 5

Powered by WordPress & Theme by Anders Norén