Merge pull request #4836 from okxlin/feat/questdb

Add QuestDB app
This commit is contained in:
okxlin
2026-07-15 22:29:47 +08:00
committed by GitHub
17 changed files with 858 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
PANEL_APP_PORT_HTTP=9000
PANEL_APP_PORT_PG=8812
QUESTDB_HTTP_USER=admin
QUESTDB_HTTP_PASSWORD=replace-with-at-least-32-safe-characters
QUESTDB_PG_USER=admin
QUESTDB_PG_PASSWORD=replace-with-another-32-safe-characters
QUESTDB_TELEMETRY_ENABLED=false
APP_DATA_DIR=./data
CONTAINER_NAME=questdb
+139
View File
@@ -0,0 +1,139 @@
additionalProperties:
formFields:
- default: 9000
edit: true
envKey: PANEL_APP_PORT_HTTP
labelEn: Web and HTTP API Port
labelZh: Web 与 HTTP API 端口
label:
en: Web and HTTP API Port
zh: Web 与 HTTP API 端口
zh-Hant: Web 與 HTTP API 連接埠
ja: Web および HTTP API ポート
ko: Web 및 HTTP API 포트
ru: Порт Web и HTTP API
ms: Port Web dan API HTTP
pt-br: Porta Web e da API HTTP
required: true
rule: paramPort
type: number
- default: 8812
edit: true
envKey: PANEL_APP_PORT_PG
labelEn: PGWire Port
labelZh: PGWire 端口
label:
en: PGWire Port
zh: PGWire 端口
zh-Hant: PGWire 連接埠
ja: PGWire ポート
ko: PGWire 포트
ru: Порт PGWire
ms: Port PGWire
pt-br: Porta PGWire
required: true
rule: paramPort
type: number
- default: admin
edit: true
envKey: QUESTDB_HTTP_USER
labelEn: HTTP Username
labelZh: HTTP 用户名
label:
en: HTTP Username
zh: HTTP 用户名
zh-Hant: HTTP 使用者名稱
ja: HTTP ユーザー名
ko: HTTP 사용자 이름
ru: Имя пользователя HTTP
ms: Nama Pengguna HTTP
pt-br: Usuário HTTP
required: true
rule: paramCommon
type: text
- default: generate
edit: true
envKey: QUESTDB_HTTP_PASSWORD
labelEn: HTTP Password
labelZh: HTTP 密码
label:
en: HTTP Password
zh: HTTP 密码
zh-Hant: HTTP 密碼
ja: HTTP パスワード
ko: HTTP 비밀번호
ru: Пароль HTTP
ms: Kata Laluan HTTP
pt-br: Senha HTTP
required: true
type: password
- default: admin
edit: true
envKey: QUESTDB_PG_USER
labelEn: PGWire Username
labelZh: PGWire 用户名
label:
en: PGWire Username
zh: PGWire 用户名
zh-Hant: PGWire 使用者名稱
ja: PGWire ユーザー名
ko: PGWire 사용자 이름
ru: Имя пользователя PGWire
ms: Nama Pengguna PGWire
pt-br: Usuário PGWire
required: true
rule: paramCommon
type: text
- default: generate
edit: true
envKey: QUESTDB_PG_PASSWORD
labelEn: PGWire Password
labelZh: PGWire 密码
label:
en: PGWire Password
zh: PGWire 密码
zh-Hant: PGWire 密碼
ja: PGWire パスワード
ko: PGWire 비밀번호
ru: Пароль PGWire
ms: Kata Laluan PGWire
pt-br: Senha PGWire
required: true
type: password
- default: "false"
edit: true
envKey: QUESTDB_TELEMETRY_ENABLED
labelEn: Enable Anonymous Telemetry
labelZh: 启用匿名遥测
label:
en: Enable Anonymous Telemetry
zh: 启用匿名遥测
zh-Hant: 啟用匿名遙測
ja: 匿名テレメトリを有効化
ko: 익명 원격 측정 활성화
ru: Включить анонимную телеметрию
ms: Dayakan Telemetri Tanpa Nama
pt-br: Ativar telemetria anônima
required: true
type: select
values:
- label: "false"
value: "false"
- label: "true"
value: "true"
- default: ./data
edit: true
envKey: APP_DATA_DIR
labelEn: Data Directory
labelZh: 数据目录
label:
en: Data Directory
zh: 数据目录
zh-Hant: 資料目錄
ja: データディレクトリ
ko: 데이터 디렉터리
ru: Каталог данных
ms: Direktori Data
pt-br: Diretório de dados
required: true
type: text
+1
View File
@@ -0,0 +1 @@
+34
View File
@@ -0,0 +1,34 @@
services:
questdb:
image: "questdb/questdb:9.4.3"
container_name: ${CONTAINER_NAME}
init: true
restart: unless-stopped
networks:
- 1panel-network
ports:
- "${PANEL_APP_PORT_HTTP}:9000"
- "${PANEL_APP_PORT_PG}:8812"
environment:
- QDB_HTTP_USER=${QUESTDB_HTTP_USER}
- QDB_HTTP_PASSWORD=${QUESTDB_HTTP_PASSWORD}
- QDB_PG_USER=${QUESTDB_PG_USER}
- QDB_PG_PASSWORD=${QUESTDB_PG_PASSWORD}
- QDB_TELEMETRY_ENABLED=${QUESTDB_TELEMETRY_ENABLED}
- QDB_LINE_TCP_ENABLED=false
- QDB_HTTP_PESSIMISTIC_HEALTH_CHECK_ENABLED=true
- QDB_HTTP_HEALTH_CHECK_AUTHENTICATION_REQUIRED=false
volumes:
- "${APP_DATA_DIR}:/var/lib/questdb"
healthcheck:
test: ["CMD-SHELL", "curl --fail --silent http://127.0.0.1:9003/status >/dev/null"]
interval: 30s
timeout: 5s
start_period: 60s
retries: 5
labels:
createdBy: "Apps"
networks:
1panel-network:
external: true
+189
View File
@@ -0,0 +1,189 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}"
fail() {
printf '%s\n' "$1" >&2
exit 1
}
strip_matching_quotes() {
local value="$1"
if [[ ${#value} -ge 2 ]]; then
if [[ "${value:0:1}" == '"' && "${value: -1}" == '"' ]]; then
value="${value:1:${#value}-2}"
elif [[ "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then
value="${value:1:${#value}-2}"
fi
fi
printf '%s\n' "$value"
}
env_has_key() {
local key="$1"
[[ -f "$ENV_FILE" ]] && grep -qE "^${key}=" "$ENV_FILE"
}
read_env_value() {
local key="$1"
local value=""
if env_has_key "$key"; then
value="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2-)"
fi
strip_matching_quotes "$value"
}
read_effective_value() {
local key="$1"
if [[ -v "$key" ]]; then
strip_matching_quotes "${!key}"
else
read_env_value "$key"
fi
}
username_is_safe() {
local value="$1"
[[ "$value" =~ ^[A-Za-z_][A-Za-z0-9_.-]{0,62}$ ]]
}
secret_is_safe() {
local value="$1"
[[ "$value" =~ ^[-A-Za-z0-9._~!@#%^\&*+=:,/?]{32,}$ ]]
}
generate_secret() {
local value=""
if command -v openssl >/dev/null 2>&1; then
value="$(openssl rand -hex 32)"
elif [[ -r /dev/urandom ]] && command -v od >/dev/null 2>&1; then
value="$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n')"
fi
[[ "$value" =~ ^[0-9a-f]{64}$ ]] || fail "Unable to generate a secure QuestDB password"
printf '%s\n' "$value"
}
write_secret_cache() {
local cache_file="$1"
local value="$2"
local temp_file
umask 077
temp_file="$(mktemp "${cache_file}.tmp.XXXXXX")"
printf '%s\n' "$value" > "$temp_file"
chmod 600 "$temp_file"
mv -f -- "$temp_file" "$cache_file"
}
set_env_value() {
local key="$1"
local value="$2"
local env_dir
local temp_file
[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found"
[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link"
env_dir="$(dirname "$ENV_FILE")"
temp_file="$(mktemp "${env_dir}/.questdb-env.tmp.XXXXXX")"
awk -v key="$key" -v value="$value" '
BEGIN { written = 0 }
$0 ~ "^" key "=" {
if (!written) {
print key "=" value
written = 1
}
next
}
{ print }
END {
if (!written) {
print key "=" value
}
}
' "$ENV_FILE" > "$temp_file"
chmod --reference="$ENV_FILE" "$temp_file"
mv -f -- "$temp_file" "$ENV_FILE"
}
resolve_secret() {
local env_key="$1"
local cache_file="$2"
local value
local cached_value=""
[[ ! -L "$cache_file" ]] || fail "QuestDB password cache must not be a symbolic link"
if [[ -e "$cache_file" && ! -f "$cache_file" ]]; then
fail "QuestDB password cache must be a regular file"
fi
if [[ -s "$cache_file" ]]; then
cached_value="$(sed -n '1p' "$cache_file")"
secret_is_safe "$cached_value" || fail "Persisted QuestDB password is invalid"
fi
value="$(read_effective_value "$env_key")"
if [[ -z "$value" || "$value" == "generate" ]]; then
if [[ -n "$cached_value" ]]; then
value="$cached_value"
else
value="$(generate_secret)"
fi
else
secret_is_safe "$value" || fail "${env_key} must contain at least 32 safe characters"
fi
write_secret_cache "$cache_file" "$value"
set_env_value "$env_key" "$value"
}
[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found"
[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link"
app_data_dir_explicit=false
if [[ ${APP_DATA_DIR+x} ]]; then
APP_DATA_DIR_RAW="$APP_DATA_DIR"
app_data_dir_explicit=true
elif env_has_key APP_DATA_DIR; then
APP_DATA_DIR_RAW="$(read_env_value APP_DATA_DIR)"
app_data_dir_explicit=true
else
APP_DATA_DIR_RAW="./data"
fi
APP_DATA_DIR_RAW="$(strip_matching_quotes "$APP_DATA_DIR_RAW")"
if [[ "$app_data_dir_explicit" == "true" && -z "$APP_DATA_DIR_RAW" ]]; then
fail "APP_DATA_DIR must not be empty"
fi
case "$APP_DATA_DIR_RAW" in
/*)
APP_DATA_DIR_ABS="$(realpath -m -- "$APP_DATA_DIR_RAW")"
;;
*)
APP_DATA_DIR_ABS="$(realpath -m -- "${ROOT_DIR}/${APP_DATA_DIR_RAW#./}")"
case "$APP_DATA_DIR_ABS" in
"${ROOT_DIR}" | "${ROOT_DIR}"/*) ;;
*) fail "Relative APP_DATA_DIR must stay inside the application directory" ;;
esac
;;
esac
[[ "$APP_DATA_DIR_ABS" != "/" ]] || fail "APP_DATA_DIR must not be the filesystem root"
if [[ -e "$APP_DATA_DIR_ABS" && ! -d "$APP_DATA_DIR_ABS" ]]; then
fail "APP_DATA_DIR must be a directory"
fi
mkdir -p -- "$APP_DATA_DIR_ABS"
http_user="$(read_effective_value QUESTDB_HTTP_USER)"
pg_user="$(read_effective_value QUESTDB_PG_USER)"
username_is_safe "$http_user" || fail "QUESTDB_HTTP_USER contains unsupported characters"
username_is_safe "$pg_user" || fail "QUESTDB_PG_USER contains unsupported characters"
resolve_secret QUESTDB_HTTP_PASSWORD "${APP_DATA_DIR_ABS}/.questdb_http_password"
resolve_secret QUESTDB_PG_PASSWORD "${APP_DATA_DIR_ABS}/.questdb_pg_password"
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
if command -v docker-compose >/dev/null 2>&1; then
docker-compose down --volumes --remove-orphans
else
docker compose down --volumes --remove-orphans
fi
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
exec bash "${SCRIPT_DIR}/init.sh"
+57
View File
@@ -0,0 +1,57 @@
# QuestDB
## 产品介绍
QuestDB 是一个面向实时分析的开源时序数据库,提供高吞吐写入、低延迟 SQL 查询、Web Console、REST API、InfluxDB Line Protocol 和 PostgreSQL Wire Protocol。
## 主要功能
- 面向时序数据的列式存储、WAL 和分区管理
- Web Console、REST API 与 SQL 查询
- 通过 HTTP 接收 InfluxDB Line Protocol 数据
- 兼容 PostgreSQL Wire Protocol 的查询接口
- 快照、检查点和持久化数据目录
## 访问说明
- Web Console、REST API 和 ILP-over-HTTP 共用默认端口 `9000`。
- PostgreSQL Wire Protocol 默认使用端口 `8812`,数据库名为 `qdb`。
- 本应用默认启用 HTTP Basic 认证和 PGWire 密码认证,两个接口使用独立用户名与密码。
- QuestDB 的 ILP TCP 端口 `9009` 默认不提供简单的用户名/密码保护。本应用为减少未认证写入面,默认关闭且不发布该端口;常见写入场景可直接使用已受 HTTP Basic 保护的 ILP-over-HTTP。
- 内部 `9003` 端口仅用于容器健康检查,不发布到宿主机。健康状态端点不要求业务凭据,其他 HTTP 接口仍受 Basic 认证保护。
## 数据与安全
- QuestDB 的完整数据根目录持久化到安装表单选择的数据目录,其中包含 `db`、`conf`、`public`、快照和检查点等运行数据。
- 安装脚本会生成并持久化独立的 HTTP 与 PGWire 密码。数据目录中的 `.questdb_http_password` 和 `.questdb_pg_password` 用于防止 1Panel 升级回放安装参数时轮换密码,请与数据库一起备份。
- 官方镜像入口以 root 启动,用于检查数据目录属主,然后以 UID/GID `10001:10001` 运行 QuestDB。首次挂载既有数据目录时,上游入口可能调整其已知数据子目录的属主;迁移前应先备份并核对权限。
- Web Console 和协议端口本身不提供 HTTPS。对外访问时应使用 HTTPS 反向代理、限制端口来源,并按需配置网络层 TLS。
- 默认关闭匿名遥测,可在安装表单中按需开启。
## 升级说明
- 升级前停止写入并完整备份数据目录及两个密码缓存文件。
- 固定版与 `latest` 使用相同的数据路径和配置键,可通过 1Panel 执行跨版本升级。
- 数据格式迁移可能在启动阶段完成。生产数据应先在副本环境验证目标版本,并在升级过程中等待健康检查通过后再恢复流量。
## Introduction
QuestDB is an open-source time-series database for high-throughput ingestion and low-latency SQL analytics. This package exposes the authenticated Web/REST/ILP-over-HTTP endpoint and PGWire endpoint, while disabling unauthenticated ILP TCP by default.
Back up the full data directory, `.questdb_http_password` and `.questdb_pg_password` before upgrades. Public deployments should place the Web endpoint behind HTTPS and restrict direct access to the database ports.
## Features
- Column-oriented time-series storage, WAL and partition management
- Web Console, REST API and SQL queries
- Authenticated InfluxDB Line Protocol over HTTP
- PostgreSQL Wire Protocol compatibility
- Persistent checkpoints and snapshots
## 参考资料
- Docker 部署:<https://questdb.com/docs/deployment/docker/>
- 配置选项:<https://questdb.com/docs/configuration/>
- 安全指南:<https://questdb.com/docs/operations/secure/>
- 源码仓库:<https://github.com/questdb/questdb>
- 官方镜像:<https://hub.docker.com/r/questdb/questdb>
+31
View File
@@ -0,0 +1,31 @@
name: QuestDB
tags:
- 数据库
title: 面向实时分析的高性能开源时序数据库
description: 面向实时分析的高性能开源时序数据库
additionalProperties:
key: questdb
name: QuestDB
tags:
- Database
shortDescZh: 面向实时分析的高性能开源时序数据库
shortDescEn: A high-performance open-source time-series database for real-time analytics
description:
en: A high-performance open-source time-series database for real-time analytics
zh: 面向实时分析的高性能开源时序数据库
zh-Hant: 面向即時分析的高效能開源時序資料庫
ja: リアルタイム分析向けの高性能オープンソース時系列データベース
ko: 실시간 분석을 위한 고성능 오픈 소스 시계열 데이터베이스
ru: Высокопроизводительная база данных временных рядов с открытым исходным кодом для аналитики в реальном времени
ms: Pangkalan data siri masa sumber terbuka berprestasi tinggi untuk analitik masa nyata
pt-br: Banco de dados de séries temporais de alto desempenho e código aberto para análises em tempo real
type: website
crossVersionUpdate: true
limit: 0
recommend: 0
website: https://questdb.com/
github: https://github.com/questdb/questdb
document: https://questdb.com/docs/deployment/docker/
architectures:
- amd64
- arm64
+9
View File
@@ -0,0 +1,9 @@
PANEL_APP_PORT_HTTP=9000
PANEL_APP_PORT_PG=8812
QUESTDB_HTTP_USER=admin
QUESTDB_HTTP_PASSWORD=replace-with-at-least-32-safe-characters
QUESTDB_PG_USER=admin
QUESTDB_PG_PASSWORD=replace-with-another-32-safe-characters
QUESTDB_TELEMETRY_ENABLED=false
APP_DATA_DIR=./data
CONTAINER_NAME=questdb
+139
View File
@@ -0,0 +1,139 @@
additionalProperties:
formFields:
- default: 9000
edit: true
envKey: PANEL_APP_PORT_HTTP
labelEn: Web and HTTP API Port
labelZh: Web 与 HTTP API 端口
label:
en: Web and HTTP API Port
zh: Web 与 HTTP API 端口
zh-Hant: Web 與 HTTP API 連接埠
ja: Web および HTTP API ポート
ko: Web 및 HTTP API 포트
ru: Порт Web и HTTP API
ms: Port Web dan API HTTP
pt-br: Porta Web e da API HTTP
required: true
rule: paramPort
type: number
- default: 8812
edit: true
envKey: PANEL_APP_PORT_PG
labelEn: PGWire Port
labelZh: PGWire 端口
label:
en: PGWire Port
zh: PGWire 端口
zh-Hant: PGWire 連接埠
ja: PGWire ポート
ko: PGWire 포트
ru: Порт PGWire
ms: Port PGWire
pt-br: Porta PGWire
required: true
rule: paramPort
type: number
- default: admin
edit: true
envKey: QUESTDB_HTTP_USER
labelEn: HTTP Username
labelZh: HTTP 用户名
label:
en: HTTP Username
zh: HTTP 用户名
zh-Hant: HTTP 使用者名稱
ja: HTTP ユーザー名
ko: HTTP 사용자 이름
ru: Имя пользователя HTTP
ms: Nama Pengguna HTTP
pt-br: Usuário HTTP
required: true
rule: paramCommon
type: text
- default: generate
edit: true
envKey: QUESTDB_HTTP_PASSWORD
labelEn: HTTP Password
labelZh: HTTP 密码
label:
en: HTTP Password
zh: HTTP 密码
zh-Hant: HTTP 密碼
ja: HTTP パスワード
ko: HTTP 비밀번호
ru: Пароль HTTP
ms: Kata Laluan HTTP
pt-br: Senha HTTP
required: true
type: password
- default: admin
edit: true
envKey: QUESTDB_PG_USER
labelEn: PGWire Username
labelZh: PGWire 用户名
label:
en: PGWire Username
zh: PGWire 用户名
zh-Hant: PGWire 使用者名稱
ja: PGWire ユーザー名
ko: PGWire 사용자 이름
ru: Имя пользователя PGWire
ms: Nama Pengguna PGWire
pt-br: Usuário PGWire
required: true
rule: paramCommon
type: text
- default: generate
edit: true
envKey: QUESTDB_PG_PASSWORD
labelEn: PGWire Password
labelZh: PGWire 密码
label:
en: PGWire Password
zh: PGWire 密码
zh-Hant: PGWire 密碼
ja: PGWire パスワード
ko: PGWire 비밀번호
ru: Пароль PGWire
ms: Kata Laluan PGWire
pt-br: Senha PGWire
required: true
type: password
- default: "false"
edit: true
envKey: QUESTDB_TELEMETRY_ENABLED
labelEn: Enable Anonymous Telemetry
labelZh: 启用匿名遥测
label:
en: Enable Anonymous Telemetry
zh: 启用匿名遥测
zh-Hant: 啟用匿名遙測
ja: 匿名テレメトリを有効化
ko: 익명 원격 측정 활성화
ru: Включить анонимную телеметрию
ms: Dayakan Telemetri Tanpa Nama
pt-br: Ativar telemetria anônima
required: true
type: select
values:
- label: "false"
value: "false"
- label: "true"
value: "true"
- default: ./data
edit: true
envKey: APP_DATA_DIR
labelEn: Data Directory
labelZh: 数据目录
label:
en: Data Directory
zh: 数据目录
zh-Hant: 資料目錄
ja: データディレクトリ
ko: 데이터 디렉터리
ru: Каталог данных
ms: Direktori Data
pt-br: Diretório de dados
required: true
type: text
+1
View File
@@ -0,0 +1 @@
+34
View File
@@ -0,0 +1,34 @@
services:
questdb:
image: "questdb/questdb:latest"
container_name: ${CONTAINER_NAME}
init: true
restart: unless-stopped
networks:
- 1panel-network
ports:
- "${PANEL_APP_PORT_HTTP}:9000"
- "${PANEL_APP_PORT_PG}:8812"
environment:
- QDB_HTTP_USER=${QUESTDB_HTTP_USER}
- QDB_HTTP_PASSWORD=${QUESTDB_HTTP_PASSWORD}
- QDB_PG_USER=${QUESTDB_PG_USER}
- QDB_PG_PASSWORD=${QUESTDB_PG_PASSWORD}
- QDB_TELEMETRY_ENABLED=${QUESTDB_TELEMETRY_ENABLED}
- QDB_LINE_TCP_ENABLED=false
- QDB_HTTP_PESSIMISTIC_HEALTH_CHECK_ENABLED=true
- QDB_HTTP_HEALTH_CHECK_AUTHENTICATION_REQUIRED=false
volumes:
- "${APP_DATA_DIR}:/var/lib/questdb"
healthcheck:
test: ["CMD-SHELL", "curl --fail --silent http://127.0.0.1:9003/status >/dev/null"]
interval: 30s
timeout: 5s
start_period: 60s
retries: 5
labels:
createdBy: "Apps"
networks:
1panel-network:
external: true
+189
View File
@@ -0,0 +1,189 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}"
fail() {
printf '%s\n' "$1" >&2
exit 1
}
strip_matching_quotes() {
local value="$1"
if [[ ${#value} -ge 2 ]]; then
if [[ "${value:0:1}" == '"' && "${value: -1}" == '"' ]]; then
value="${value:1:${#value}-2}"
elif [[ "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then
value="${value:1:${#value}-2}"
fi
fi
printf '%s\n' "$value"
}
env_has_key() {
local key="$1"
[[ -f "$ENV_FILE" ]] && grep -qE "^${key}=" "$ENV_FILE"
}
read_env_value() {
local key="$1"
local value=""
if env_has_key "$key"; then
value="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2-)"
fi
strip_matching_quotes "$value"
}
read_effective_value() {
local key="$1"
if [[ -v "$key" ]]; then
strip_matching_quotes "${!key}"
else
read_env_value "$key"
fi
}
username_is_safe() {
local value="$1"
[[ "$value" =~ ^[A-Za-z_][A-Za-z0-9_.-]{0,62}$ ]]
}
secret_is_safe() {
local value="$1"
[[ "$value" =~ ^[-A-Za-z0-9._~!@#%^\&*+=:,/?]{32,}$ ]]
}
generate_secret() {
local value=""
if command -v openssl >/dev/null 2>&1; then
value="$(openssl rand -hex 32)"
elif [[ -r /dev/urandom ]] && command -v od >/dev/null 2>&1; then
value="$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n')"
fi
[[ "$value" =~ ^[0-9a-f]{64}$ ]] || fail "Unable to generate a secure QuestDB password"
printf '%s\n' "$value"
}
write_secret_cache() {
local cache_file="$1"
local value="$2"
local temp_file
umask 077
temp_file="$(mktemp "${cache_file}.tmp.XXXXXX")"
printf '%s\n' "$value" > "$temp_file"
chmod 600 "$temp_file"
mv -f -- "$temp_file" "$cache_file"
}
set_env_value() {
local key="$1"
local value="$2"
local env_dir
local temp_file
[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found"
[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link"
env_dir="$(dirname "$ENV_FILE")"
temp_file="$(mktemp "${env_dir}/.questdb-env.tmp.XXXXXX")"
awk -v key="$key" -v value="$value" '
BEGIN { written = 0 }
$0 ~ "^" key "=" {
if (!written) {
print key "=" value
written = 1
}
next
}
{ print }
END {
if (!written) {
print key "=" value
}
}
' "$ENV_FILE" > "$temp_file"
chmod --reference="$ENV_FILE" "$temp_file"
mv -f -- "$temp_file" "$ENV_FILE"
}
resolve_secret() {
local env_key="$1"
local cache_file="$2"
local value
local cached_value=""
[[ ! -L "$cache_file" ]] || fail "QuestDB password cache must not be a symbolic link"
if [[ -e "$cache_file" && ! -f "$cache_file" ]]; then
fail "QuestDB password cache must be a regular file"
fi
if [[ -s "$cache_file" ]]; then
cached_value="$(sed -n '1p' "$cache_file")"
secret_is_safe "$cached_value" || fail "Persisted QuestDB password is invalid"
fi
value="$(read_effective_value "$env_key")"
if [[ -z "$value" || "$value" == "generate" ]]; then
if [[ -n "$cached_value" ]]; then
value="$cached_value"
else
value="$(generate_secret)"
fi
else
secret_is_safe "$value" || fail "${env_key} must contain at least 32 safe characters"
fi
write_secret_cache "$cache_file" "$value"
set_env_value "$env_key" "$value"
}
[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found"
[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link"
app_data_dir_explicit=false
if [[ ${APP_DATA_DIR+x} ]]; then
APP_DATA_DIR_RAW="$APP_DATA_DIR"
app_data_dir_explicit=true
elif env_has_key APP_DATA_DIR; then
APP_DATA_DIR_RAW="$(read_env_value APP_DATA_DIR)"
app_data_dir_explicit=true
else
APP_DATA_DIR_RAW="./data"
fi
APP_DATA_DIR_RAW="$(strip_matching_quotes "$APP_DATA_DIR_RAW")"
if [[ "$app_data_dir_explicit" == "true" && -z "$APP_DATA_DIR_RAW" ]]; then
fail "APP_DATA_DIR must not be empty"
fi
case "$APP_DATA_DIR_RAW" in
/*)
APP_DATA_DIR_ABS="$(realpath -m -- "$APP_DATA_DIR_RAW")"
;;
*)
APP_DATA_DIR_ABS="$(realpath -m -- "${ROOT_DIR}/${APP_DATA_DIR_RAW#./}")"
case "$APP_DATA_DIR_ABS" in
"${ROOT_DIR}" | "${ROOT_DIR}"/*) ;;
*) fail "Relative APP_DATA_DIR must stay inside the application directory" ;;
esac
;;
esac
[[ "$APP_DATA_DIR_ABS" != "/" ]] || fail "APP_DATA_DIR must not be the filesystem root"
if [[ -e "$APP_DATA_DIR_ABS" && ! -d "$APP_DATA_DIR_ABS" ]]; then
fail "APP_DATA_DIR must be a directory"
fi
mkdir -p -- "$APP_DATA_DIR_ABS"
http_user="$(read_effective_value QUESTDB_HTTP_USER)"
pg_user="$(read_effective_value QUESTDB_PG_USER)"
username_is_safe "$http_user" || fail "QUESTDB_HTTP_USER contains unsupported characters"
username_is_safe "$pg_user" || fail "QUESTDB_PG_USER contains unsupported characters"
resolve_secret QUESTDB_HTTP_PASSWORD "${APP_DATA_DIR_ABS}/.questdb_http_password"
resolve_secret QUESTDB_PG_PASSWORD "${APP_DATA_DIR_ABS}/.questdb_pg_password"
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
if command -v docker-compose >/dev/null 2>&1; then
docker-compose down --volumes --remove-orphans
else
docker compose down --volumes --remove-orphans
fi
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
exec bash "${SCRIPT_DIR}/init.sh"
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB