diff --git a/apps/questdb/9.4.3/.env.sample b/apps/questdb/9.4.3/.env.sample
new file mode 100644
index 000000000..18379ca1f
--- /dev/null
+++ b/apps/questdb/9.4.3/.env.sample
@@ -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
diff --git a/apps/questdb/9.4.3/data.yml b/apps/questdb/9.4.3/data.yml
new file mode 100644
index 000000000..22b8778d9
--- /dev/null
+++ b/apps/questdb/9.4.3/data.yml
@@ -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
diff --git a/apps/questdb/9.4.3/data/.gitkeep b/apps/questdb/9.4.3/data/.gitkeep
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/apps/questdb/9.4.3/data/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/apps/questdb/9.4.3/docker-compose.yml b/apps/questdb/9.4.3/docker-compose.yml
new file mode 100644
index 000000000..e5f0ab805
--- /dev/null
+++ b/apps/questdb/9.4.3/docker-compose.yml
@@ -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
diff --git a/apps/questdb/9.4.3/scripts/init.sh b/apps/questdb/9.4.3/scripts/init.sh
new file mode 100755
index 000000000..ceed52f87
--- /dev/null
+++ b/apps/questdb/9.4.3/scripts/init.sh
@@ -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"
diff --git a/apps/questdb/9.4.3/scripts/uninstall.sh b/apps/questdb/9.4.3/scripts/uninstall.sh
new file mode 100755
index 000000000..5a3f6bd6b
--- /dev/null
+++ b/apps/questdb/9.4.3/scripts/uninstall.sh
@@ -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
diff --git a/apps/questdb/9.4.3/scripts/upgrade.sh b/apps/questdb/9.4.3/scripts/upgrade.sh
new file mode 100755
index 000000000..593b31667
--- /dev/null
+++ b/apps/questdb/9.4.3/scripts/upgrade.sh
@@ -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"
diff --git a/apps/questdb/README.md b/apps/questdb/README.md
new file mode 100644
index 000000000..2a28ec1bb
--- /dev/null
+++ b/apps/questdb/README.md
@@ -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 部署:
+- 配置选项:
+- 安全指南:
+- 源码仓库:
+- 官方镜像:
diff --git a/apps/questdb/data.yml b/apps/questdb/data.yml
new file mode 100644
index 000000000..9b77ed434
--- /dev/null
+++ b/apps/questdb/data.yml
@@ -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
diff --git a/apps/questdb/latest/.env.sample b/apps/questdb/latest/.env.sample
new file mode 100644
index 000000000..18379ca1f
--- /dev/null
+++ b/apps/questdb/latest/.env.sample
@@ -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
diff --git a/apps/questdb/latest/data.yml b/apps/questdb/latest/data.yml
new file mode 100644
index 000000000..22b8778d9
--- /dev/null
+++ b/apps/questdb/latest/data.yml
@@ -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
diff --git a/apps/questdb/latest/data/.gitkeep b/apps/questdb/latest/data/.gitkeep
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/apps/questdb/latest/data/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/apps/questdb/latest/docker-compose.yml b/apps/questdb/latest/docker-compose.yml
new file mode 100644
index 000000000..0532b6a24
--- /dev/null
+++ b/apps/questdb/latest/docker-compose.yml
@@ -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
diff --git a/apps/questdb/latest/scripts/init.sh b/apps/questdb/latest/scripts/init.sh
new file mode 100755
index 000000000..ceed52f87
--- /dev/null
+++ b/apps/questdb/latest/scripts/init.sh
@@ -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"
diff --git a/apps/questdb/latest/scripts/uninstall.sh b/apps/questdb/latest/scripts/uninstall.sh
new file mode 100755
index 000000000..5a3f6bd6b
--- /dev/null
+++ b/apps/questdb/latest/scripts/uninstall.sh
@@ -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
diff --git a/apps/questdb/latest/scripts/upgrade.sh b/apps/questdb/latest/scripts/upgrade.sh
new file mode 100755
index 000000000..593b31667
--- /dev/null
+++ b/apps/questdb/latest/scripts/upgrade.sh
@@ -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"
diff --git a/apps/questdb/logo.png b/apps/questdb/logo.png
new file mode 100644
index 000000000..ca83aa19a
Binary files /dev/null and b/apps/questdb/logo.png differ