diff --git a/.gitignore b/.gitignore index 16784c2..7621b0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ *.old docker-compose.custom*.yml /docker-compose*.yml +# init-env.sh などで生成される .env はパスワードを含むため Git 管理対象外 +# (init-env.sh --force 時に作られるバックアップ .env.bak. も対象) +**/.env +**/.env.bak.* diff --git a/Environment.md b/Environment.md index 76726e8..d7ec390 100644 --- a/Environment.md +++ b/Environment.md @@ -4,13 +4,17 @@ 以下では各構成で共通的な環境変数について示します。 各構成で独自の環境変数が定義されている場合もありますので、それぞれの説明を参照してください。 +> **デフォルト値の位置付け**: 以下の表に示す `DATABASE_URL` / `AMQP_URL` / `CACHE_URL` 等のデフォルト値は **kompira コンテナイメージ自体の既定値** であり、docker compose を経由せずコンテナを直接動かす場合などに参照されるものです。**ke2-docker の compose ファイルでは別途上書きされており**、内部 `postgres` / `rabbitmq` / `redis` コンテナへの接続情報 (`DATABASE_USER` / `DATABASE_PASSWORD` / `DATABASE_NAME` / `AMQP_USER` / `AMQP_PASSWORD` から組み立てた URL、または利用者が指定した `DATABASE_URL` / `AMQP_URL`) が実際の接続先として使われます。 + | 環境変数名 | デフォルト | 意味 | |-----------------------|-----------------------------------------------------|----------------------------| | `HOSTNAME` | (下記参照) | ホスト名 | | `KOMPIRA_IMAGE_NAME` | "kompira.azurecr.io/kompira-enterprise" | Kompira イメージ | | `KOMPIRA_IMAGE_TAG` | (下記参照) | Kompira タグ | | `DATABASE_URL` | "pgsql://kompira@//var/run/postgresql/kompira" | データベースの接続先 | +| `DATABASE_USER` / `DATABASE_PASSWORD` / `DATABASE_NAME` | (下記参照) | DATABASE_URL の組み立て要素 | | `AMQP_URL` | "amqp://guest:guest@localhost:5672" | メッセージキューの接続先 | +| `AMQP_USER` / `AMQP_PASSWORD` | (下記参照) | AMQP_URL の組み立て要素 | | `CACHE_URL` | "redis://localhost:6379" | キャッシュの接続先 | | `TZ` | "Asia/Tokyo" | タイムゾーン | | `LANGUAGE_CODE` | "ja" | 言語設定 | @@ -43,6 +47,56 @@ Kompira に必要なサブシステムである、データベースやメッセ 参考: https://django-environ.readthedocs.io/en/latest/types.html#environ-env-db-url +なお ke2-docker の compose ファイルでは、`DATABASE_URL` / `AMQP_URL` を直接指定しなかった場合、後述の `DATABASE_USER` 等の個別変数から URL を自動構築する仕組みになっています。 + +## DATABASE_USER / DATABASE_PASSWORD / DATABASE_NAME / AMQP_USER / AMQP_PASSWORD + +接続情報を URL 文字列として `DATABASE_URL` / `AMQP_URL` で指定する代わりに、ユーザ名・パスワード・データベース名を個別の環境変数で指定できます。 + +| 環境変数名 | デフォルト | 用途 | +|-----------------------|-------------|-------------------------------------------------------------------------| +| `DATABASE_USER` | "kompira" | DATABASE_URL のユーザ名。single/basic では postgres コンテナの初期ユーザ名にも適用 | +| `DATABASE_PASSWORD` | "kompira" | DATABASE_URL のパスワード。single/basic では postgres コンテナの初期パスワードにも適用 | +| `DATABASE_NAME` | "kompira" | DATABASE_URL のデータベース名。single/basic では postgres コンテナの初期 DB 名にも適用 | +| `AMQP_USER` | "guest" | AMQP_URL のユーザ名。rabbitmq コンテナの初期ユーザ名にも適用 | +| `AMQP_PASSWORD` | "guest" | AMQP_URL のパスワード。rabbitmq コンテナの初期パスワードにも適用 | + +これらのデフォルト値は互換性のための仮の値です。本番運用前に必ず変更してください。 + +> **コンテナ初期化への適用は初回起動時のみ**: `DATABASE_USER` / `DATABASE_PASSWORD` / `DATABASE_NAME` の postgres コンテナへの適用 (`POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB`)、および `AMQP_USER` / `AMQP_PASSWORD` の rabbitmq コンテナへの適用 (`RABBITMQ_DEFAULT_USER` / `RABBITMQ_DEFAULT_PASS`) は、それぞれデータボリュームが空の **初回起動時のみ** 行われます。既存のデータボリュームを残したまま値を変更しても、DB 内のユーザ・パスワード・データベース名は更新されません。後から変更する手順は管理者マニュアル「資格情報管理」章を参照してください。 + +`DATABASE_URL` または `AMQP_URL` を直接指定した場合、**アプリケーション側 (kompira / kengine / jobmngrd) の接続先** には指定された URL がそのまま使われ、対応する個別変数 (`DATABASE_USER` 等または `AMQP_USER` 等) は **接続先 URL の組み立てには使われなくなります**。一方、single/basic 構成における **postgres / rabbitmq コンテナの初期化** には個別変数の値が引き続き使われます (上記「コンテナ初期化への適用は初回起動時のみ」参照)。アプリ側と内部コンテナ側で別の値になると認証不整合が起きるため、`DATABASE_URL` / `AMQP_URL` を独自に指定する場合は個別変数も対応する値に揃えるか、または `DATABASE_URL` / `AMQP_URL` 側を内部コンテナの初期値と一致させてください。 + +### URL 安全でない文字を含む資格情報の扱い + +`DATABASE_USER` / `DATABASE_PASSWORD` / `DATABASE_NAME` および `AMQP_USER` / `AMQP_PASSWORD` のいずれかに URL 安全でない文字 (RFC 3986 unreserved set 以外。`@` `:` `/` `%` 等の予約文字や空白などを含む) を含む値を設定する場合、組み立てられた URL に正しく埋め込めない問題が発生します。本節はパスワードに限らず、ユーザ名・DB 名にも適用されます (`DATABASE_URL` / `AMQP_URL` を直接指定する場合は、利用者があらかじめパーセントエンコードして埋め込む必要があります)。 + +URL に文字列を埋め込む際のエンコード規約は [RFC 3986 (URI: Generic Syntax)](https://datatracker.ietf.org/doc/html/rfc3986) で定められています。`DATABASE_URL` / `AMQP_URL` の userinfo (ユーザ名・パスワード部分) や path (DB 名部分) で **URL の区切り文字として解釈される文字** (主に `@` `:` `/` `?` `#` `%` や空白等) を **データの一部として** 埋め込む場合は、`%XX` (XX は ASCII コードの 16 進数表記) でパーセントエンコードする必要があります。安全側に倒すなら、RFC 3986 §2.3 で「unreserved」と定義された英数字および `-` `_` `.` `~` 以外の文字をすべてエンコードしておけば確実です。許容される文字とエンコード規則の詳細は [RFC 3986 §3.2.1 (userinfo)](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1) / [§3.3 (path)](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3) を参照してください。 + +主要な変換例: + +| 文字 | エンコード後 | 文字 | エンコード後 | +|------|--------------|------------|--------------| +| `@` | `%40` | `?` | `%3F` | +| `:` | `%3A` | `#` | `%23` | +| `/` | `%2F` | `%` | `%25` | +| `+` | `%2B` | `&` | `%26` | +| `=` | `%3D` | ` ` (空白) | `%20` | + +実例: 実パスワード `p@ss:w0rd` を `DATABASE_URL` に埋め込む場合は、`pgsql://user:p%40ss%3Aw0rd@host:5432/db` のように `p@ss:w0rd` → `p%40ss%3Aw0rd` に変換して指定します。 + +各環境変数の挙動: + +- **`DATABASE_USER` / `DATABASE_PASSWORD` / `DATABASE_NAME`**: + - `DATABASE_URL` を直接指定するか、`scripts/init-env.sh` で `.env` を生成する場合は、URL 安全でない文字を含む値も利用可能です (受け取り側の django-environ が URL デコードするため、kompira コンテナイメージのバージョン依存もありません)。 + - 一方、これらの個別変数を指定して compose 側に `DATABASE_URL` を組み立てさせる場合は、いずれも URL に未エンコードのまま埋め込まれるため、RFC 3986 unreserved set (英数字と `-_.~`) のみを使用してください。 +- **`AMQP_USER` / `AMQP_PASSWORD`**: kompira コンテナイメージが **v2.0.5.post2 以降** である必要があります (AMQP_URL の userinfo を URL デコードする修正が必要なため)。v2.0.5.post1 以前のイメージを利用する場合、`AMQP_USER` / `AMQP_PASSWORD` には RFC 3986 unreserved set (英数字と `-_.~`) のみを使用してください。 + - また DB 系同様、これらの個別変数を指定して compose 側に `AMQP_URL` を組み立てさせる場合も、URL に未エンコードのまま埋め込まれるため、unreserved set のみを使用してください (`scripts/init-env.sh` 経由なら自動でエンコードされます)。 + +URL 安全でない文字を含むユーザ名・パスワード・DB 名を安全に設定するには、`scripts/url-encode.sh` で個別の文字列をエンコードするか、`scripts/init-env.sh` で `.env` を自動生成する方法を推奨します (init-env.sh は内部で url-encode.sh を利用して URL エンコードを自動処理します)。詳細は管理者マニュアル「資格情報管理」章を参照してください。 + +また、シェルコマンドで値全体をシングルクオートで囲む形式 (`DATABASE_URL='pgsql://...'` / `AMQP_URL='amqps://...'` 等) を使う場合、シェル特殊文字 (`#` / 空白 / `$` / `!` 等) を含む値でも安全にコピペできますが、値自体に `'` (シングルクオート) を含む場合は閉じてしまうため別途エスケープが必要です (`'\''` での逐次切替、または `scripts/init-env.sh` で `.env` を生成して利用する等)。詳細な対処方法は管理者マニュアル「資格情報管理」§5 を参照してください。 + ## TZ / LANGUAGE_CODE 各コンテナのタイムゾーンと言語コードを設定します。 diff --git a/ke2/cluster/swarm/docker-compose.override.yml b/ke2/cluster/swarm/docker-compose.override.yml index 2a308fb..41a5133 100644 --- a/ke2/cluster/swarm/docker-compose.override.yml +++ b/ke2/cluster/swarm/docker-compose.override.yml @@ -16,6 +16,12 @@ x-multi-replicated: placement: max_replicas_per_node: 1 +# Swarm 構成では postgres コンテナを起動せず外部 DB を前提とするため、DATABASE_URL の指定を必須とする。 +# setup_stack.sh は DATABASE_URL を事前に export してから docker compose config を呼ぶため、 +# スクリプト経由のセットアップでは発火しない。スクリプト未経由のセットアップで未指定の場合に即エラーで停止する。 +x-required-db-env: &required-db-env + DATABASE_URL: '${DATABASE_URL:?DATABASE_URL must be set: e.g., pgsql://user:password@host:5432/dbname}' + services: redis: hostname: "re-{{.Node.Hostname}}" @@ -37,19 +43,22 @@ services: jobmngrd: hostname: "jm-{{.Node.Hostname}}" environment: - - AMQP_URL=${AMQP_URL:-amqp://guest:guest@mq-<<.Node.Hostname>>:5672} + <<: *required-db-env + AMQP_URL: ${AMQP_URL:-amqp://${AMQP_USER:-guest}:${AMQP_PASSWORD:-guest}@mq-<<.Node.Hostname>>:5672} logging: *default-logging deploy: *multi-replicated kengine: hostname: "ke-{{.Node.Hostname}}" environment: - - AMQP_URL=${AMQP_URL:-amqp://guest:guest@mq-<<.Node.Hostname>>:5672} + <<: *required-db-env + AMQP_URL: ${AMQP_URL:-amqp://${AMQP_USER:-guest}:${AMQP_PASSWORD:-guest}@mq-<<.Node.Hostname>>:5672} logging: *default-logging deploy: *multi-replicated kompira: hostname: "ap-{{.Node.Hostname}}" environment: - - AMQP_URL=${AMQP_URL:-amqp://guest:guest@mq-<<.Node.Hostname>>:5672} + <<: *required-db-env + AMQP_URL: ${AMQP_URL:-amqp://${AMQP_USER:-guest}:${AMQP_PASSWORD:-guest}@mq-<<.Node.Hostname>>:5672} logging: *default-logging deploy: *multi-replicated nginx: diff --git a/ke2/extra/jobmngrd/docker-compose.override.yml b/ke2/extra/jobmngrd/docker-compose.override.yml index 5813a4a..0d9a2cc 100644 --- a/ke2/extra/jobmngrd/docker-compose.override.yml +++ b/ke2/extra/jobmngrd/docker-compose.override.yml @@ -1,7 +1,11 @@ +# 外部 jobmngrd 構成では KE2.0 本体側で rabbitmqctl add_user により追加した +# 専用ユーザ・パスワードを AMQP_URL に埋め込んで接続するため、AMQP_URL の指定は必須。 +# 未指定の場合は docker compose 起動時にエラーで停止する (サンプル値が暗黙に +# 使われて認証失敗するのを防ぐ目的)。 services: jobmngrd: logging: driver: local environment: - - AMQP_URL=${AMQP_URL:-amqps://kompira:kompira@${AMQP_HOST:-rabbitmq}:5671} + AMQP_URL: '${AMQP_URL:?AMQP_URL must be set: e.g., amqps://:@:5671}' restart: always diff --git a/ke2/services/jobmngrd.yml b/ke2/services/jobmngrd.yml index e0c5008..26ea03f 100644 --- a/ke2/services/jobmngrd.yml +++ b/ke2/services/jobmngrd.yml @@ -4,9 +4,9 @@ services: hostname: jm-${HOSTNAME:?HOSTNAME must be set} init: true environment: - DATABASE_URL: ${DATABASE_URL:-pgsql://kompira:kompira@${DATABASE_HOST:-postgres}:5432/kompira} + DATABASE_URL: ${DATABASE_URL:-pgsql://${DATABASE_USER:-kompira}:${DATABASE_PASSWORD:-kompira}@${DATABASE_HOST:-postgres}:5432/${DATABASE_NAME:-kompira}} CACHE_URL: ${CACHE_URL:-redis://redis:6379} - AMQP_URL: ${AMQP_URL:-amqp://guest:guest@rabbitmq:5672} + AMQP_URL: ${AMQP_URL:-amqp://${AMQP_USER:-guest}:${AMQP_PASSWORD:-guest}@rabbitmq:5672} TZ: ${TZ:-Asia/Tokyo} LOGGING_NAME: jobmngrd LOGGING_STREAM: ${JOBMNGRD_LOGGING_STREAM:-${LOGGING_STREAM:-true}} diff --git a/ke2/services/kompira.yml b/ke2/services/kompira.yml index d4dc37b..02b4880 100644 --- a/ke2/services/kompira.yml +++ b/ke2/services/kompira.yml @@ -15,8 +15,8 @@ x-kompira-common-settings: x-kompira-common-environ: &kompira-common-environ - DATABASE_URL: ${DATABASE_URL:-pgsql://kompira:kompira@${DATABASE_HOST:-postgres}:5432/kompira} - AMQP_URL: ${AMQP_URL:-amqp://guest:guest@rabbitmq:5672} + DATABASE_URL: ${DATABASE_URL:-pgsql://${DATABASE_USER:-kompira}:${DATABASE_PASSWORD:-kompira}@${DATABASE_HOST:-postgres}:5432/${DATABASE_NAME:-kompira}} + AMQP_URL: ${AMQP_URL:-amqp://${AMQP_USER:-guest}:${AMQP_PASSWORD:-guest}@rabbitmq:5672} CACHE_URL: ${CACHE_URL:-redis://redis:6379} LANGUAGE_CODE: ${LANGUAGE_CODE:-ja} TZ: ${TZ:-Asia/Tokyo} diff --git a/ke2/services/nginx.yml b/ke2/services/nginx.yml index e4cea2f..f266669 100644 --- a/ke2/services/nginx.yml +++ b/ke2/services/nginx.yml @@ -3,10 +3,10 @@ services: image: registry.hub.docker.com/library/nginx:1.27-alpine hostname: nx-${HOSTNAME:?HOSTNAME must be set} environment: - - TZ=${TZ:-Asia/Tokyo} - - NGINX_ENVSUBST_FILTER=^KOMPIRA - - KOMPIRA_HOST=${KOMPIRA_HOST:-kompira} - - KOMPIRA_PORT=${KOMPIRA_PORT:-8000} + TZ: ${TZ:-Asia/Tokyo} + NGINX_ENVSUBST_FILTER: ^KOMPIRA + KOMPIRA_HOST: ${KOMPIRA_HOST:-kompira} + KOMPIRA_PORT: ${KOMPIRA_PORT:-8000} configs: # MEMO: /etc/nginx/templates/*.template は起動時に環境変数が展開されて /etc/nginx/conf.d/* に書き出される - source: nginx-config diff --git a/ke2/services/postgres.yml b/ke2/services/postgres.yml index 16201b0..744c8e7 100644 --- a/ke2/services/postgres.yml +++ b/ke2/services/postgres.yml @@ -4,8 +4,13 @@ services: image: registry.hub.docker.com/library/postgres:16.3-alpine hostname: db-${HOSTNAME:?HOSTNAME must be set} environment: - POSTGRES_PASSWORD: kompira - POSTGRES_USER: kompira + # POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB は **PGDATA ボリュームが空の初回起動時のみ** + # 反映される (公式 postgres イメージの仕様)。後から DATABASE_USER / DATABASE_PASSWORD / + # DATABASE_NAME を変更しても DB 内のユーザ・パスワード・データベース名は更新されない。 + # 既存環境で変更したい場合の手順は管理者マニュアル「資格情報管理」章を参照。 + POSTGRES_USER: ${DATABASE_USER:-kompira} + POSTGRES_PASSWORD: ${DATABASE_PASSWORD:-kompira} + POSTGRES_DB: ${DATABASE_NAME:-kompira} TZ: ${TZ:-Asia/Tokyo} volumes: - ${PGDATA:-kompira_pg16}:/var/lib/postgresql/data diff --git a/ke2/services/rabbitmq.yml b/ke2/services/rabbitmq.yml index 84a082c..c4b5d9b 100644 --- a/ke2/services/rabbitmq.yml +++ b/ke2/services/rabbitmq.yml @@ -4,6 +4,12 @@ services: hostname: mq-${HOSTNAME:?HOSTNAME must be set} environment: RABBITMQ_DATA_DIR: /var/lib/rabbitmq + # RABBITMQ_DEFAULT_USER / RABBITMQ_DEFAULT_PASS は **rabbitmq データボリュームが空の初回起動時のみ** + # 反映される (公式 rabbitmq イメージの仕様)。後から AMQP_USER / AMQP_PASSWORD を変更しても + # rabbitmq のユーザ・パスワードは更新されない。既存環境で変更したい場合の手順は + # 管理者マニュアル「資格情報管理」章を参照 (rabbitmqctl change_password 等)。 + RABBITMQ_DEFAULT_USER: ${AMQP_USER:-guest} + RABBITMQ_DEFAULT_PASS: ${AMQP_PASSWORD:-guest} TZ: ${TZ:-Asia/Tokyo} configs: - source: bootstrap-rabbitmq diff --git a/ke2/services/redis.yml b/ke2/services/redis.yml index 9c82821..17455a3 100644 --- a/ke2/services/redis.yml +++ b/ke2/services/redis.yml @@ -3,4 +3,4 @@ services: image: registry.hub.docker.com/library/redis:7.2-alpine hostname: re-${HOSTNAME:?HOSTNAME must be set} environment: - - TZ=${TZ:-Asia/Tokyo} + TZ: ${TZ:-Asia/Tokyo} diff --git a/ke2/single/extdb/README.md b/ke2/single/extdb/README.md index 84cc946..caa1f4f 100644 --- a/ke2/single/extdb/README.md +++ b/ke2/single/extdb/README.md @@ -18,24 +18,30 @@ Docker が動作しているホストサーバ、あるいは別のサーバ上 この構成では、Docker コンテナで動作するサービスからデータベースに接続するための情報を、環境変数 DATABASE_URL で指定する必要があります。 - DATABASE_URL=pgsql://<ユーザ名>:<パスワード>@<アドレス>:<ポート番号>/<データベース名> + DATABASE_URL='pgsql://:@:<ポート番号>/<データベース名>' -たとえば外部データベースの接続に必要なパラメータが以下のような場合を考えます。 +> **注: 上記のユーザ名・パスワード・データベース名・IP アドレス・ポート番号はあくまで「設定する値の形式」を示す例示です。** 任意の値を使用できますので、お使いの環境のセキュリティポリシーに合わせて設定してください。特にパスワードは推測されにくい十分に強いものを指定してください。ポート番号は PostgreSQL のデフォルト `5432` を使うことが多いですが、変更している場合はそれに合わせて指定してください。 -| 設定項目 | パラメータ例 | -| -------------- | --------------- | -| ユーザ名 | kompira | -| パスワード | kompira | -| IPアドレス | 10.20.0.XXX | -| ポート番号 | 5432 | -| データベース名 | kompira | +以下では具体的な手順例として、次の値で構成した場合の例を示します。 -この場合、環境変数 DATABASE_URL は次のように指定することになります。 +| 設定項目 | 例示値 | +| -------------- | ------------------------------------- | +| ユーザ名 | `kompira_user` | +| パスワード | `<十分に強いパスワード>` | +| IPアドレス | `10.20.0.10` | +| ポート番号 | `5432` | +| データベース名 | `kompira_db` | - DATABASE_URL=pgsql://kompira:kompira@10.20.0.XXX:5432/kompira +このとき DATABASE_URL は以下のように指定します。 + + DATABASE_URL='pgsql://kompira_user:<十分に強いパスワード>@10.20.0.10:5432/kompira_db' 実際に準備した、またはこれから準備する PostgreSQL サーバの構成に合わせて、DATABASE_URL に指定する値を準備しておいてください。 +> **ユーザ名・パスワード・DB 名に URL の区切り文字 (`@` `:` `/` `?` `#` `%` や空白等) を含める場合**: そのままだと URL の解釈が壊れるため、[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) に従いパーセントエンコード (`@` → `%40`, `:` → `%3A`, `/` → `%2F` 等) が必要です (パスワードに限らず、userinfo / path のいずれの部分も対象。安全側に倒すなら英数字と `-_.~` 以外をすべてエンコード)。詳細な変換規則と例は [Environment.md](../../../Environment.md) 「URL 安全でない文字を含む資格情報の扱い」または管理者マニュアル「資格情報管理」章を参照。 +> +> なお、上記コマンド例は値全体をシングルクオートで囲む形式のため、`#` / 空白 / `$` / `!` 等のシェル特殊文字を含むパスワードでも安全にコピペできますが、値自体に `'` (シングルクオート) を含む場合は閉じてしまうため別途エスケープが必要です (`'\''` での逐次切替、または `scripts/init-env.sh` で `.env` を生成して利用する等。詳細は管理者マニュアル「資格情報管理」§5 を参照)。 + ### PostgreSQL の準備 PostgreSQL のインストールについては準備する環境に合わせて、OS のマニュアルなどを参考にして実施してください。 @@ -43,15 +49,15 @@ PostgreSQL のインストールについては準備する環境に合わせて 以下では、PostgreSQL の最低限必要になる設定手順について記述しますので、用意した PostgreSQL サーバ上で実施してください。 詳細な設定方法については PostgreSQL のホームページや公式ドキュメントを参照してください。 -**(1) kompira ユーザーとデータベースの作成** +**(1) ユーザとデータベースの作成** -ユーザを作成します。ここではデフォルトの "kompira" という名前で作成します。パスワードの設定も求められるのでこれもデフォルトの "kompira" と入力してください。 +ユーザを作成します。ここでは例として "kompira_user" という名前で作成します。パスワードの設定も求められるので、上で準備した値を入力してください。 - $ sudo -i -u postgres createuser -d -P "kompira" + $ sudo -i -u postgres createuser -d -P "kompira_user" -上で作成したユーザをオーナーとするデータベースを作成します。ここではデフォルトの "kompira" という名前で作成します。 - - $ sudo -i -u postgres createdb --owner="kompira" --encoding=utf8 "kompira" +上で作成したユーザをオーナーとするデータベースを作成します。ここでは例として "kompira_db" という名前で作成します。 + + $ sudo -i -u postgres createdb --owner="kompira_user" --encoding=utf8 "kompira_db" **(2) PostgreSQL サーバの接続設定** @@ -63,13 +69,13 @@ postgresql.conf (RHEL系標準パッケージをインストールした場合 **(3) PostgreSQL のクライアント認証の設定** 手順 (1) で作成したユーザからパスワード接続できるように、pg_hba.conf (RHEL系標準パッケージをインストールした場合は /var/lib/pgsql/data/pg_hba.conf) に host 設定を追加してください。 -たとえば Docker が動作しているホストが PostgreSQL サーバと同じネットワークに配置している場合は、以下のような行を追加してください。 +たとえば Docker が動作しているホストが PostgreSQL サーバと同じネットワークに配置している場合は、以下のような行を追加してください (フィールドは「データベース名 ユーザ名 接続元アドレス 認証方式」)。 - host kompira kompira samenet scram-sha-256 + host kompira_db kompira_user samenet scram-sha-256 あるいは、Docker が異なるネットワークに配置されている場合は、"samenet" の部分を CIDR 形式などで指定するようにしてください。 - host kompira kompira 10.10.0.0/16 scram-sha-256 + host kompira_db kompira_user 10.10.0.0/16 scram-sha-256 これらの設定を行なった後は、一度 postgresql サービスを再起動してください。 @@ -94,9 +100,9 @@ Kompira 用データベースを新規に構築する場合は、たとえば以 $ echo -n 'xxxxxxxxxxxxxxxx' > .secret_key 続けて、以下のコマンドを実行して Kompira Enterprise 開始をします。 -このとき先に準備した環境変数 DATABASE_URL を指定するようにしてください。 +このとき先に準備した接続情報を反映した DATABASE_URL を **必ず指定** してください。未指定で起動した場合は `docker compose config` の段階で即エラー停止します。 - $ DATABASE_URL=pgsql://... docker compose up -d + $ DATABASE_URL='pgsql://:@:<ポート番号>/<データベース名>' docker compose up -d ## カスタマイズ ### 環境変数によるカスタマイズ @@ -107,14 +113,20 @@ docker compose up するときに環境変数を指定することで、簡易 この構成で指定できる環境変数を以下に示します。 -| 環境変数 | 備考 | -| ------------------ | ------------------------------------------------------------------------------------------- | -| `DATABASE_URL` | 外部データベース | -| `KOMPIRA_LOG_DIR` | ログファイルの出力先ディレクトリ(未指定の場合は kompira_log ボリューム内に出力されます) | +| 環境変数 | 備考 | +| ------------------ | ------------------------------------------------------------------------------------------------- | +| `DATABASE_URL` | 外部データベースの接続 URL。**必須**。未指定の場合は起動時にエラー停止します | +| `AMQP_USER` | 内部 RabbitMQ コンテナのユーザ名(デフォルト: `guest`) | +| `AMQP_PASSWORD` | 内部 RabbitMQ コンテナのパスワード(デフォルト: `guest`、本番運用前に変更を強く推奨) | +| `KOMPIRA_LOG_DIR` | ログファイルの出力先ディレクトリ(未指定の場合は kompira_log ボリューム内に出力されます) | + +カスタマイズ例: -カスタマイズ例: + $ DATABASE_URL='pgsql://...' AMQP_PASSWORD='strong-pw' KOMPIRA_LOG_DIR=/var/log/kompira docker compose up -d - $ DATABASE_URL=pgsql://... KOMPIRA_LOG_DIR=/var/log/kompira docker compose up -d +> **AMQP_USER / AMQP_PASSWORD に URL 安全でない文字 (`@` `:` `/` `%` や空白等、RFC 3986 unreserved 以外の文字) を含めて利用する場合**: KE2.0 コンテナイメージが **v2.0.5.post2 以降** である必要があります (AMQP_URL の userinfo を URL デコードする修正に依存)。v2.0.5.post1 以前のイメージでは `AMQP_USER` / `AMQP_PASSWORD` は英数字と `-_.~` のみ使用してください。 +> +> 強度の高いパスワードを `.env` で簡単に管理したい場合は、ヘルパースクリプト `../../../scripts/init-env.sh` で `.env` を自動生成できます (任意ステップ)。 ### 詳細なカスタマイズ diff --git a/ke2/single/extdb/docker-compose.override.yml b/ke2/single/extdb/docker-compose.override.yml index 2e9f93c..8c293f5 100644 --- a/ke2/single/extdb/docker-compose.override.yml +++ b/ke2/single/extdb/docker-compose.override.yml @@ -4,6 +4,11 @@ x-logging: # options: # tag: "docker.{{.Name}}" +# 外部DB構成では postgres コンテナを起動しないため、DATABASE_URL の指定を必須とする。 +# 未指定の場合は docker compose up 時にエラーで停止する。 +x-required-db-env: &required-db-env + DATABASE_URL: '${DATABASE_URL:?DATABASE_URL must be set: e.g., pgsql://user:password@host:5432/dbname}' + services: redis: logging: *default-logging @@ -12,11 +17,15 @@ services: logging: *default-logging restart: always jobmngrd: + environment: + <<: *required-db-env logging: *default-logging depends_on: - rabbitmq restart: always kengine: + environment: + <<: *required-db-env volumes: - type: bind source: ./.secret_key @@ -27,6 +36,8 @@ services: - rabbitmq restart: always kompira: + environment: + <<: *required-db-env volumes: - type: bind source: ./.secret_key diff --git a/scripts/init-env.sh b/scripts/init-env.sh new file mode 100755 index 0000000..ec8309c --- /dev/null +++ b/scripts/init-env.sh @@ -0,0 +1,454 @@ +#! /bin/sh +# +# init-env.sh +# +# ke2-docker の .env ファイルを生成するオプショナルなセットアップスクリプト。 +# +# 用途: +# compose の既定値 (kompira/guest) より強度の高いパスワードを簡単に +# 設定したい場合に使用する。 +# - single/basic: .env に DATABASE_USER / DATABASE_PASSWORD / DATABASE_NAME / +# AMQP_USER / AMQP_PASSWORD のリテラル値と、URL エンコード済みの +# DATABASE_URL / AMQP_URL の両方を書き出すため、特殊文字を含む値でも動作する。 +# - single/extdb: 利用者が --database-url で渡した URL をそのまま .env に +# 書き出し、AMQP_USER / AMQP_PASSWORD のリテラル値と URL エンコード済みの +# AMQP_URL を併せて書き出す (DB 側のユーザ名・パスワード・DB 名は URL に +# 利用者責任で URL エンコード済みで含めてもらう想定)。 +# +# 任意ステップ: +# このスクリプトの実行は任意。実行せずに直接 docker compose up しても +# 従来通り既定値 (kompira/guest) で動作する。 +# +# Usage: +# cd ke2/single/basic # or ke2/single/extdb +# ../../../scripts/init-env.sh [OPTIONS] +# +# Options: +# --force 既存の .env を上書きする +# --db-user '' DB ユーザ名を指定 (デフォルト: kompira) +# --db-password '' DB パスワードを指定 (未指定時はランダム生成) +# --db-name '' DB 名を指定 (デフォルト: kompira) +# --mq-user '' RabbitMQ ユーザ名を指定 (デフォルト: guest) +# --mq-password '' RabbitMQ パスワードを指定 (未指定時はランダム生成) +# --database-url '' (extdb 専用) 外部 DB の URL。extdb では必須 +# --config 構成を明示指定 (未指定時はカレントディレクトリから自動判定) +# 指定可能な値: single/basic, single/extdb +# --help ヘルプを表示 +# +# Supported configurations (パス末尾 2 階層で識別): +# - single/basic : 完全サポート (パスワードを生成または指定、URL は自動構成) +# - single/extdb : AMQP 側のみ自動生成。DATABASE_URL は --database-url で必須指定 +# +# Not supported (実行するとエラーで終了): +# - cluster/swarm : setup_stack.sh を利用 (本スクリプトの対象外) +# - cloud/azureci : ARM テンプレートで資格情報を管理 +# - extra/jobmngrd : 外部接続専用 (.env 生成は不要) +# +# 動作要件: +# - POSIX 互換 shell (/bin/sh) と POSIX 標準ユーティリティ群 +# (sed / od / tr / dd / date / chmod / printf など) +# - 同ディレクトリに url-encode.sh が存在すること +# - パスワード未指定時は openssl コマンド または /dev/urandom が必要 +# +set -eu + +# url_unsafe_warning 等で使う文字クラス (`[A-Za-z0-9._~-]`) は POSIX shell の +# 文字クラスとして locale 依存に解釈され、非 `C` locale では非 ASCII 文字が +# 「unreserved」と誤判定されることがある。安定動作のため LC_ALL=C を強制し、 +# ASCII 範囲で確実に判定する (date / sed / printf 等の出力にも波及するが、 +# 本スクリプトで使う出力はすべて ASCII 範囲なので副作用はない)。 +LC_ALL=C +export LC_ALL + +# POSIX 互換のスクリプトディレクトリ検出 (readlink -f は GNU 拡張なので避ける)。 +# シンボリックリンク経由の起動は想定しないため、cd + pwd で十分。 +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +URL_ENCODE="$SCRIPT_DIR/url-encode.sh" + +# set -u 下では未設定変数の参照でスクリプトが落ちる。 +# 通常 $PWD は shell が自動設定するが、unset/絞り込み環境を想定して +# pwd の結果を CWD に明示的に取得しておき、以降は $CWD を使う。 +CWD=$(pwd) + +# 利用者が独自に追記した環境変数定義を再実行時に保持するためのマーカー行。 +# awk の完全一致 (`$0 == MARKER_LINE`) で検出するため、他のコメント行と +# 識別しやすいよう記号・大文字・アンダースコアのみで構成し、途中の空白は +# 含めない (`#` 直後の 1 空白だけは通常コメント表記との統一感のため許容)。 +MARKER_LINE="# =====KE2_DOCKER_INIT_ENV_END=====" + +print_help() { + sed -n '3,/^set -eu/{/^set -eu/d;s/^# \{0,1\}//;s/^#$//;p;}' "$0" +} + +# --- argument parsing --- +# *_SET フラグは「利用者が値付きオプションを明示指定したか」を区別するために +# 使う (random_password() 呼び出し判定や single/extdb の整合性検査で必要)。 +# 空文字列 (`--xxx ""`) は後段の require_nonempty で事前に reject されるため、 +# *_SET=1 のときは必ず非空値が入る前提で扱える。 +FORCE=0 +DB_USER=kompira +DB_USER_SET=0 +DB_PASSWORD= +DB_PASSWORD_SET=0 +DB_NAME=kompira +DB_NAME_SET=0 +MQ_USER=guest +MQ_PASSWORD= +MQ_PASSWORD_SET=0 +DATABASE_URL_ARG= +CONFIG= + +# set -eu 下では `$2` 直接参照だと値省略時に "unbound variable" となり原因が +# 分かりにくいため、値付きオプションでは require_value で明示的に検査する。 +require_value() { + # $1: option name, $2: number of remaining args (= "$#") + if [ "$2" -lt 2 ]; then + echo "ERROR: option $1 requires a value" >&2 + exit 1 + fi +} + +# 値付きオプションに空文字列を渡すと、後段で .env に空値が書き出され、 +# 内部 postgres / rabbitmq コンテナの初期化や DATABASE_URL の組み立てが +# 破綻する (postgres は POSTGRES_PASSWORD="" で起動拒否、rabbitmq は +# 初期ユーザの認証が成立しない等)。意図しない空文字指定を防ぐため、 +# 値付きオプションでは require_nonempty で空文字を明示的に reject する。 +require_nonempty() { + # $1: option name, $2: given value + if [ -z "$2" ]; then + echo "ERROR: option $1 requires a non-empty value" >&2 + exit 1 + fi +} + +while [ $# -gt 0 ]; do + case "$1" in + --force) FORCE=1 ;; + --db-user) require_value "$1" "$#"; require_nonempty "$1" "$2"; DB_USER=$2; DB_USER_SET=1; shift ;; + --db-password) require_value "$1" "$#"; require_nonempty "$1" "$2"; DB_PASSWORD=$2; DB_PASSWORD_SET=1; shift ;; + --db-name) require_value "$1" "$#"; require_nonempty "$1" "$2"; DB_NAME=$2; DB_NAME_SET=1; shift ;; + --mq-user) require_value "$1" "$#"; require_nonempty "$1" "$2"; MQ_USER=$2; shift ;; + --mq-password) require_value "$1" "$#"; require_nonempty "$1" "$2"; MQ_PASSWORD=$2; MQ_PASSWORD_SET=1; shift ;; + --database-url) require_value "$1" "$#"; require_nonempty "$1" "$2"; DATABASE_URL_ARG=$2; shift ;; + --config) require_value "$1" "$#"; require_nonempty "$1" "$2"; CONFIG=$2; shift ;; + --help|-h) print_help; exit 0 ;; + *) echo "ERROR: unknown option: $1" >&2; echo "Use --help for usage." >&2; exit 1 ;; + esac + shift +done + +# --- prerequisite checks --- +if [ ! -x "$URL_ENCODE" ]; then + echo "ERROR: url-encode.sh not found or not executable at $URL_ENCODE" >&2 + exit 1 +fi + +# --- detect configuration --- +# 末尾 2 階層 (例: "single/basic", "cluster/swarm") を識別キーに使う。 +# basename だけだと将来 "cluster/basic" 等が追加されたときに区別できないため。 +# .env はカレントディレクトリに生成されるため、cwd_key は --config 指定の +# 有無に関わらず必ず検査する (誤った場所への .env 生成や明示的に非対象と +# している構成 (cluster/swarm 等) からの実行を防ぐ目的)。 +parent_base=$(basename "$(dirname "$CWD")") +cwd_base=$(basename "$CWD") +cwd_key="$parent_base/$cwd_base" +case "$cwd_key" in + single/basic|single/extdb) + ;; + cluster/swarm) + echo "ERROR: cluster/swarm is not supported by this script." >&2 + echo " Use setup_stack.sh for Swarm cluster setup. See:" >&2 + echo " https://fixpoint.github.io/ke2-admin-manual/setup/cluster/swarm/" >&2 + exit 1 ;; + cloud/azureci) + echo "ERROR: cloud/azureci is not supported by this script." >&2 + echo " Credentials are managed via ARM template parameters." >&2 + exit 1 ;; + extra/jobmngrd) + echo "ERROR: extra/jobmngrd is not supported by this script." >&2 + echo " Set AMQP_URL directly when running docker compose up." >&2 + exit 1 ;; + *) + echo "ERROR: cannot detect configuration from current directory ($CWD)." >&2 + echo " Detected path key: '$cwd_key' (expected: single/basic or single/extdb)." >&2 + echo " Run this script from ke2/single/basic or ke2/single/extdb." >&2 + exit 1 ;; +esac + +# --config 未指定なら cwd_key を採用、指定済みなら cwd_key との一致を検査する。 +# (--config だけでカレントディレクトリ検査をスキップすると、利用者が誤った場所で +# .env を生成してしまう恐れがあるため。) +if [ -z "$CONFIG" ]; then + CONFIG=$cwd_key +elif [ "$CONFIG" != "$cwd_key" ]; then + echo "ERROR: --config '$CONFIG' does not match current directory ($cwd_key)." >&2 + echo " Either move to the matching directory or drop --config." >&2 + exit 1 +fi + +case "$CONFIG" in + single/basic|single/extdb) ;; + *) echo "ERROR: unsupported config: $CONFIG (supported: single/basic, single/extdb)" >&2; exit 1 ;; +esac + +# --- option / config 整合性検証 --- +# 構成と関係ないオプションを指定した場合は明示的にエラーにする +# (silent ignore だと利用者が「効いた」と勘違いするため)。 +case "$CONFIG" in + single/basic) + if [ -n "$DATABASE_URL_ARG" ]; then + echo "ERROR: --database-url is only valid for single/extdb config." >&2 + echo " In single/basic, DATABASE_URL is auto-constructed from" >&2 + echo " --db-user / --db-password / --db-name (defaults: kompira)." >&2 + exit 1 + fi + ;; + single/extdb) + # extdb では DB ユーザ・パスワード・DB 名は --database-url の中に + # URL エンコード済みで埋め込む形式のため、個別の --db-* オプションは + # 意味を持たない。 + if [ "$DB_USER_SET" = 1 ] || [ "$DB_PASSWORD_SET" = 1 ] || [ "$DB_NAME_SET" = 1 ]; then + echo "ERROR: --db-user / --db-password / --db-name are not used in single/extdb config." >&2 + echo " Include the DB user/password/name (URL-encoded) inside --database-url instead." >&2 + exit 1 + fi + ;; +esac + +# --- check existing .env --- +if [ -f .env ] && [ $FORCE -eq 0 ]; then + echo "ERROR: .env already exists in $CWD" >&2 + echo " Use --force to overwrite." >&2 + exit 1 +fi + +# --force で上書きする場合は以下を実施: +# (1) 既存 .env をタイムスタンプ付きでバックアップ +# (2) 既存 .env にマーカー行があれば、マーカー以降の独自追記分を +# 退避して新しい .env に結合する (再実行で独自設定が消えないように) +# (3) マーカー行がない (旧版 or 手書きの .env など) 場合は warning を出力。 +# 独自設定があった場合はバックアップから手動転記する必要あり。 +USER_CUSTOM="" +if [ -f .env ] && [ "$FORCE" -eq 1 ]; then + BACKUP=".env.bak.$(date '+%Y%m%d-%H%M%S')" + cp -p .env "$BACKUP" + # cp -p は元 .env の mode を引き継ぐが、元が緩いパーミッションだった + # 場合にバックアップにもパスワード等の credentials が残るのは危険。 + # バックアップは常に 600 に切り詰める。 + chmod 600 "$BACKUP" + echo "Backed up existing .env to $BACKUP (permissions: 600)" + + if grep -qFx "$MARKER_LINE" .env; then + # マーカー行の次の行から末尾までを退避。`$(...)` は末尾改行を + # 削除するためダミー終端文字 _ で保持し、最後に ${...%_} で取り除く。 + USER_CUSTOM=$(awk -v m="$MARKER_LINE" 'f { print } $0 == m { f = 1 }' .env; printf '_') + USER_CUSTOM=${USER_CUSTOM%_} + else + echo "WARNING: existing .env has no marker line; any custom additions" >&2 + echo " will NOT be carried over to the regenerated .env." >&2 + echo " Recover from $BACKUP if needed." >&2 + fi +fi + +# --- extdb-specific: require --database-url --- +if [ "$CONFIG" = single/extdb ] && [ -z "$DATABASE_URL_ARG" ]; then + echo "ERROR: extdb configuration requires --database-url ''" >&2 + echo " The external DB URL must be specified explicitly. The embedded password" >&2 + echo " must already be URL-encoded (RFC 3986)." >&2 + echo " Example 1: pre-encoded password (single quotes are safe to copy)" >&2 + echo " --database-url 'pgsql://kompira:p%40ss%3Aw0rd@10.20.0.10:5432/kompira'" >&2 + echo " Example 2: encode with url-encode.sh first, then substitute" >&2 + echo " ENC=\$(../../../scripts/url-encode.sh '')" >&2 + echo " --database-url \"pgsql://kompira:\$ENC@10.20.0.10:5432/kompira\"" >&2 + exit 1 +fi + +# --- random password generator (POSIX) --- +random_password() { + if command -v openssl >/dev/null 2>&1; then + openssl rand -hex 16 + elif [ -r /dev/urandom ]; then + # head -c は POSIX 必須ではないため dd を使う (dd / od / tr は POSIX 必須)。 + # od のオプションは POSIX 形式 (-A n -t x1) で分けて書く (-An -tx1 は GNU 拡張)。 + dd if=/dev/urandom bs=16 count=1 2>/dev/null | od -A n -t x1 | tr -d ' \n' + else + echo "ERROR: cannot generate random password" >&2 + echo " Neither 'openssl' command nor /dev/urandom is available." >&2 + echo " Specify passwords explicitly with --db-password and --mq-password." >&2 + exit 1 + fi +} + +# --- generate or use provided passwords --- +# 利用者がリテラル指定したパスワードに URL に安全でない文字 (RFC 3986 +# unreserved set 以外: 予約文字 @ : / 等に加え、空白などの unsafe 文字も含む) +# が含まれる場合、kompira コンテナイメージは v2.0.5.post2 以降が必要となる +# ため、利用者が気付けるよう警告を出力する (継続実行は妨げない)。 +url_unsafe_warning() { + var_name="$1" + value="$2" + case "$value" in + *[!A-Za-z0-9._~-]*) + echo "WARNING: $var_name contains URL-unsafe characters (chars outside RFC 3986 unreserved set)." >&2 + echo " To handle URL-unsafe characters in $var_name correctly," >&2 + echo " kompira container image must be v2.0.5.post2 or later." >&2 + echo " For older images (<=v2.0.5.post1), restrict $var_name to" >&2 + echo " alphanumerics and '-_.~' only." >&2 + ;; + esac +} + +# AMQP_USER / AMQP_PASSWORD は basic / extdb いずれの構成でも .env に書き出す。 +[ "$MQ_PASSWORD_SET" = 0 ] && MQ_PASSWORD=$(random_password) + +# AMQP_URL の userinfo に埋め込む AMQP_USER / AMQP_PASSWORD は、受け取り側 +# (kompira コンテナ) が URL デコードできる v2.0.5.post2 以降が必要なので、 +# URL 安全でない文字を含む場合は両方を警告対象にする。 +url_unsafe_warning AMQP_USER "$MQ_USER" +url_unsafe_warning AMQP_PASSWORD "$MQ_PASSWORD" + +# 改行 (LF / CR) を含む値は .env が壊れる/追加エントリ注入の原因になるため拒否。 +# dotenv_quote は \ と " しかエスケープしないので、改行は ${var} 行の終端と +# 解釈されてしまう。 +# 注: $() は末尾改行を削除するため、ダミー終端文字 _ を付けてから ${...%_} で +# 取り除くことで改行/CR をリテラル値として保持する。 +NL=$(printf '\n_'); NL=${NL%_} +CR=$(printf '\r_'); CR=${CR%_} +reject_newline() { + var_name="$1" + value="$2" + case "$value" in + *"$NL"*|*"$CR"*) + echo "ERROR: $var_name contains newline or CR characters." >&2 + echo " Newline/CR cannot be safely embedded in .env values." >&2 + exit 1 + ;; + esac +} +reject_newline AMQP_USER "$MQ_USER" +reject_newline AMQP_PASSWORD "$MQ_PASSWORD" + +# --- dotenv value escaping --- +# Compose v2 の .env パーサ仕様: ダブルクオートで囲んだ値は中身の \ と " +# だけがエスケープシーケンスとして解釈される。リテラル値に空白・#・'・" +# などを含んでも .env パースが壊れないよう、全値を minimal エスケープして +# ダブルクオートで包む形に統一する。 +dotenv_quote() { + val=$(printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g') + printf '"%s"' "$val" +} + +# AMQP は basic / extdb 共通で .env に書き出す +MQ_USER_ENC=$("$URL_ENCODE" "$MQ_USER") +MQ_PASSWORD_ENC=$("$URL_ENCODE" "$MQ_PASSWORD") +MQ_USER_DQ=$(dotenv_quote "$MQ_USER") +MQ_PASSWORD_DQ=$(dotenv_quote "$MQ_PASSWORD") +MQ_URL_DQ=$(dotenv_quote "amqp://$MQ_USER_ENC:$MQ_PASSWORD_ENC@rabbitmq:5672") + +# DB 関連は構成ごとに分岐: +# - basic: 内部 postgres コンテナ用に DB_USER / DB_PASSWORD / DB_NAME を生成し +# URL エンコード + dotenv_quote して .env に書き出す +# - extdb: DATABASE_URL_ARG をそのまま dotenv_quote して .env に書き出す +# (DB ユーザ名・パスワードは URL に既に含まれているのでスクリプト側で +# ランダム生成や URL エンコードは不要) +# なお AMQP 関連 (MQ_PASSWORD) は構成共通で扱い、--mq-password 未指定時は +# どちらの構成でも random_password() が呼ばれる。openssl / /dev/urandom の +# 双方が無い最小環境では --mq-password の明示指定が必要。 +case "$CONFIG" in + single/basic) + # DATABASE_PASSWORD: 本スクリプトは DATABASE_URL を URL エンコード済みの形で + # .env に出力するため、特殊文字を含むパスワードも DATABASE_URL 経由で正しく + # 動作する (受け取り側の django-environ が unquote するため、kompira-v2 の + # バージョンにも依存しない)。AMQP_PASSWORD と異なりバージョン依存の警告は + # 不要なので url_unsafe_warning は呼ばない。 + [ "$DB_PASSWORD_SET" = 0 ] && DB_PASSWORD=$(random_password) + reject_newline DATABASE_USER "$DB_USER" + reject_newline DATABASE_PASSWORD "$DB_PASSWORD" + reject_newline DATABASE_NAME "$DB_NAME" + DB_USER_ENC=$("$URL_ENCODE" "$DB_USER") + DB_PASSWORD_ENC=$("$URL_ENCODE" "$DB_PASSWORD") + DB_NAME_ENC=$("$URL_ENCODE" "$DB_NAME") + DB_USER_DQ=$(dotenv_quote "$DB_USER") + DB_PASSWORD_DQ=$(dotenv_quote "$DB_PASSWORD") + DB_NAME_DQ=$(dotenv_quote "$DB_NAME") + DB_URL_DQ=$(dotenv_quote "pgsql://$DB_USER_ENC:$DB_PASSWORD_ENC@postgres:5432/$DB_NAME_ENC") + ;; + single/extdb) + reject_newline DATABASE_URL "$DATABASE_URL_ARG" + EXTDB_URL_DQ=$(dotenv_quote "$DATABASE_URL_ARG") + ;; +esac + +# --- generate .env --- +# `%z` (オフセット) や `-u` (UTC 強制) はいずれも POSIX 必須ではないため、 +# ローカルタイム + タイムゾーン名 (`%Z` は POSIX 必須) の形式で出力する。 +TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S %Z') + +# 新規作成の .env が world-readable にならないよう umask を絞る (新規作成のみ有効) +umask 077 + +{ + # ヘッダ (共通) + echo "# Generated by ke2-docker init-env.sh at $TIMESTAMP" + echo "# Configuration: $CONFIG" + echo "" + + # データベース関連 (CONFIG により差分あり) + case "$CONFIG" in + single/basic) + cat < .env + +# --force で既存ファイルを上書きしたケースでは umask が効かないので、 +# 書き込み後に明示的にパーミッションを 600 に揃える。 +chmod 600 .env + +echo "Generated $CWD/.env (permissions: 600)" +echo "Run 'docker compose up -d' to start ke2." diff --git a/scripts/url-encode.sh b/scripts/url-encode.sh new file mode 100755 index 0000000..6f8934e --- /dev/null +++ b/scripts/url-encode.sh @@ -0,0 +1,69 @@ +#! /bin/sh +# +# url-encode.sh +# +# 任意の文字列を RFC 3986 unreserved set (英数字と "-_.~") 以外を +# パーセントエンコードした URL エンコード形式で標準出力に書き出す。 +# +# DATABASE_URL / AMQP_URL に埋め込む文字列のエスケープに利用する。 +# パスワードに限らず、ユーザ名・データベース名など URL に埋め込むあらゆる +# 文字列に使える汎用ヘルパー。 +# +# Usage: +# $ ./scripts/url-encode.sh '' +# +# Examples: +# $ ./scripts/url-encode.sh 'p@ss:w0rd' +# p%40ss%3Aw0rd +# +# $ ./scripts/url-encode.sh 'user@example.com' +# user%40example.com +# +# $ ./scripts/url-encode.sh 'db name with spaces' +# db%20name%20with%20spaces +# +# 動作要件: +# - POSIX 互換 shell (/bin/sh) +# - 外部コマンド依存なし (printf のみ使用) +# +# 制限: +# - ASCII 範囲の文字列を入力として想定。多バイト UTF-8 などの非 ASCII 入力 +# に対する挙動はシェル実装によって異なるため、ASCII の範囲で使用すること。 +# - 引数なしでは usage を表示して終了する。 +# +set -eu + +# RFC 3986 unreserved set のチェック (`[A-Za-z0-9._~-]`) は POSIX shell の +# 文字クラスとして locale 依存に解釈されるため、非 `C` locale では非 ASCII +# 文字が「unreserved」と誤判定されることがある。安定動作のため LC_ALL=C を +# 強制し、ASCII 範囲で確実にエンコード判定する。 +LC_ALL=C +export LC_ALL + +if [ $# -ne 1 ]; then + printf "Usage: %s ''\n" "$0" >&2 + printf " Outputs URL-encoded form of on stdout.\n" >&2 + printf " Use single quotes to avoid shell expansion of special characters.\n" >&2 + exit 1 +fi + +s="$1" +while [ -n "$s" ]; do + # POSIX trick: extract the first character of $s without external commands. + # ${s#?} = $s with the first character removed + # ${s%X} = $s with X removed from the end + # combine them to get the first character only. + c=${s%"${s#?}"} + case "$c" in + [A-Za-z0-9._~-]) + printf '%s' "$c" + ;; + *) + # The "'$c" form makes printf treat $c's first byte as a numeric + # value (POSIX-defined behavior). 0xHH format gives uppercase hex. + printf '%%%02X' "'$c" + ;; + esac + s=${s#?} +done +printf '\n'