Merge pull request #4844 from okxlin/feat/directus

Add Directus app
This commit is contained in:
okxlin
2026-07-15 22:32:11 +08:00
committed by GitHub
17 changed files with 741 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
PANEL_APP_PORT_HTTP=8055
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=replace-with-a-strong-password
DIRECTUS_SECRET=replace-with-a-long-random-secret
PUBLIC_URL=/
APP_DATA_DIR=./data
CONTAINER_NAME=directus
+102
View File
@@ -0,0 +1,102 @@
additionalProperties:
formFields:
- default: 8055
edit: true
envKey: PANEL_APP_PORT_HTTP
labelEn: HTTP Port
labelZh: HTTP 端口
label:
en: HTTP Port
zh: HTTP 端口
zh-Hant: HTTP 連接埠
ja: HTTP ポート
ko: HTTP 포트
ru: Порт HTTP
ms: Port HTTP
pt-br: Porta HTTP
required: true
rule: paramPort
type: number
- default: admin@example.com
edit: true
envKey: ADMIN_EMAIL
labelEn: Administrator Email
labelZh: 管理员邮箱
label:
en: Administrator Email
zh: 管理员邮箱
zh-Hant: 管理員電子郵件
ja: 管理者メールアドレス
ko: 관리자 이메일
ru: Электронная почта администратора
ms: E-mel Pentadbir
pt-br: E-mail do administrador
required: true
type: text
- default: ""
edit: true
envKey: ADMIN_PASSWORD
labelEn: Administrator Password
labelZh: 管理员密码
label:
en: Administrator Password
zh: 管理员密码
zh-Hant: 管理員密碼
ja: 管理者パスワード
ko: 관리자 비밀번호
ru: Пароль администратора
ms: Kata Laluan Pentadbir
pt-br: Senha do administrador
required: true
rule: paramComplexity
type: password
- default: ""
edit: true
envKey: DIRECTUS_SECRET
labelEn: Instance Secret
labelZh: 实例密钥
label:
en: Instance Secret
zh: 实例密钥
zh-Hant: 執行個體密鑰
ja: インスタンスシークレット
ko: 인스턴스 비밀 키
ru: Секрет экземпляра
ms: Rahsia Instans
pt-br: Segredo da instância
random: true
required: true
rule: paramComplexity
type: password
- default: /
edit: true
envKey: PUBLIC_URL
labelEn: Public URL
labelZh: 公共访问地址
label:
en: Public URL
zh: 公共访问地址
zh-Hant: 公開存取位址
ja: 公開 URL
ko: 공개 URL
ru: Публичный URL
ms: URL Awam
pt-br: URL pública
required: false
type: text
- 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
View File
+35
View File
@@ -0,0 +1,35 @@
services:
directus:
image: "directus/directus:12.1.1"
container_name: ${CONTAINER_NAME}
restart: unless-stopped
networks:
- 1panel-network
ports:
- "${PANEL_APP_PORT_HTTP}:8055"
environment:
- SECRET=${DIRECTUS_SECRET}
- DB_CLIENT=sqlite3
- DB_FILENAME=/directus/database/data.db
- ADMIN_EMAIL=${ADMIN_EMAIL}
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
- PUBLIC_URL=${PUBLIC_URL}
- WEBSOCKETS_ENABLED=true
- TELEMETRY=false
- PROJECT_OWNER_ENABLED=false
volumes:
- "${APP_DATA_DIR}/database:/directus/database"
- "${APP_DATA_DIR}/uploads:/directus/uploads"
- "${APP_DATA_DIR}/extensions:/directus/extensions"
healthcheck:
test: ["CMD-SHELL", "wget --spider -q http://127.0.0.1:8055/server/ping || exit 1"]
interval: 10s
timeout: 5s
start_period: 30s
retries: 10
labels:
createdBy: "Apps"
networks:
1panel-network:
external: true
+163
View File
@@ -0,0 +1,163 @@
#!/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"
}
read_env_value() {
local key="$1"
local value=""
if [[ -f "$ENV_FILE" ]]; then
value="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- || true)"
fi
strip_matching_quotes "$value"
}
path_is_dotenv_safe() {
local value="$1"
case "$value" in
*$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'"*) return 1 ;;
*) return 0 ;;
esac
}
secret_is_acceptable() {
local value="$1"
[[ ${#value} -ge 32 ]] || return 1
case "$value" in
*[!A-Za-z0-9._~!@%+=:,-]*) return 1 ;;
*) return 0 ;;
esac
}
generate_secret() {
local value
value="$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n')"
[[ "$value" =~ ^[0-9a-f]{64}$ ]] || fail "Unable to generate DIRECTUS_SECRET"
printf '%s\n' "$value"
}
write_env_value() {
local key="$1"
local value="$2"
local line
local found=0
local tmp_file
tmp_file="$(mktemp "${ENV_FILE}.tmp.XXXXXX")"
chmod 600 -- "$tmp_file"
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$line" == "${key}="* ]]; then
if [[ $found -eq 0 ]]; then
printf '%s=%s\n' "$key" "$value" >>"$tmp_file"
found=1
fi
else
printf '%s\n' "$line" >>"$tmp_file"
fi
done <"$ENV_FILE"
if [[ $found -eq 0 ]]; then
printf '%s=%s\n' "$key" "$value" >>"$tmp_file"
fi
mv -f -- "$tmp_file" "$ENV_FILE"
}
persist_secret() {
local value="$1"
local tmp_file
tmp_file="$(mktemp "${APP_DATA_DIR_ABS}/.directus-secret.tmp.XXXXXX")"
chmod 600 -- "$tmp_file"
printf '%s\n' "$value" >"$tmp_file"
mv -f -- "$tmp_file" "$SECRET_FILE"
}
[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found"
[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link"
if [[ ${APP_DATA_DIR+x} ]]; then
APP_DATA_DIR_RAW="$APP_DATA_DIR"
else
APP_DATA_DIR_RAW="$(read_env_value APP_DATA_DIR)"
fi
APP_DATA_DIR_RAW="$(strip_matching_quotes "${APP_DATA_DIR_RAW:-./data}")"
[[ -n "$APP_DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be empty"
path_is_dotenv_safe "$APP_DATA_DIR_RAW" || fail "APP_DATA_DIR contains unsupported dotenv characters"
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"
[[ ! -L "$APP_DATA_DIR_ABS" ]] || fail "APP_DATA_DIR must not be a symbolic link"
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"
for directory in database uploads extensions; do
target="${APP_DATA_DIR_ABS}/${directory}"
[[ ! -L "$target" ]] || fail "$target must not be a symbolic link"
if [[ -e "$target" && ! -d "$target" ]]; then
fail "$target must be a directory"
fi
mkdir -p -- "$target"
chown 1000:1000 -- "$target"
chmod 700 -- "$target"
done
SECRET_FILE="${APP_DATA_DIR_ABS}/.directus-secret"
[[ ! -L "$SECRET_FILE" ]] || fail "$SECRET_FILE must not be a symbolic link"
if [[ -e "$SECRET_FILE" ]]; then
[[ -f "$SECRET_FILE" ]] || fail "$SECRET_FILE must be a regular file"
DIRECTUS_SECRET_FINAL="$(<"$SECRET_FILE")"
secret_is_acceptable "$DIRECTUS_SECRET_FINAL" || fail "$SECRET_FILE contains an invalid secret"
else
if [[ ${DIRECTUS_SECRET+x} ]]; then
DIRECTUS_SECRET_CANDIDATE="$DIRECTUS_SECRET"
else
DIRECTUS_SECRET_CANDIDATE="$(read_env_value DIRECTUS_SECRET)"
fi
if secret_is_acceptable "$DIRECTUS_SECRET_CANDIDATE"; then
DIRECTUS_SECRET_FINAL="$DIRECTUS_SECRET_CANDIDATE"
else
DIRECTUS_SECRET_FINAL="$(generate_secret)"
fi
persist_secret "$DIRECTUS_SECRET_FINAL"
fi
chmod 600 -- "$SECRET_FILE"
write_env_value DIRECTUS_SECRET "$DIRECTUS_SECRET_FINAL"
+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"
+69
View File
@@ -0,0 +1,69 @@
# Directus
## 产品介绍
Directus 是一个无头数据平台,可连接数据库并自动提供 REST、GraphQL、WebSocket API 以及可视化数据管理界面。本应用使用 Directus 官方镜像,以单个 Directus service 和 SQLite 数据库运行。
## 主要功能
- 连接 SQLite 数据库并自动提供 REST、GraphQL 和 WebSocket API
- 使用 Directus Studio 管理数据模型、内容、文件、权限和自动化流程
- 保存本地上传文件并加载自定义扩展
## 访问说明
- 默认容器端口为 `8055`,安装表单可修改主机端口。
- 安装时会使用表单中的管理员邮箱和强密码创建初始管理员;管理员密码由用户输入,并由 1Panel 复杂度规则校验。
- `SECRET` 用于签名访问令牌。安装脚本会把不足 32 字符的面板随机值替换为 64 位十六进制密钥,并以 `0600` 权限持久化到数据目录下的 `.directus-secret`。升级和迁移时必须连同该文件保留同一个值,否则已有会话和令牌可能失效。
- `PUBLIC_URL` 可留空,此时 Directus 使用默认相对地址 `/`。如需邮件链接、OAuth、SSO 或许可证激活,请填写实际可访问的绝对 URL,例如 `https://directus.example.com`。
## 部署拓扑
- 默认包只有一个 Directus service,不依赖 1Panel 数据库、Redis 或网站 Runtime。
- 数据库使用 SQLite,数据库文件为 `/directus/database/data.db`。
- Directus 官方 Quickstart 提供了相同的单服务 SQLite 拓扑,但明确说明其示例不是 production-ready。本包补充了随机密钥、初始管理员、持久化、健康检查并关闭默认遥测,仍定位于单节点和中小规模自托管。
- 较大生产环境或横向扩展应使用 PostgreSQL、MySQL 或 MariaDB;Redis 对缓存是推荐项,对多副本横向扩展是必需项。这些依赖适合后续通过 1Panel service selector 联动,但不属于当前默认包。不要仅修改现有 SQLite 实例的环境变量来切换数据库。
## 数据与升级
安装表单选择的数据目录包含以下持久化子目录:
- `database` → `/directus/database`
- `uploads` → `/directus/uploads`
- `extensions` → `/directus/extensions`
- `.directus-secret` → 升级时用于恢复令牌签名密钥的隐藏文件
容器以 UID/GID `1000:1000` 运行,安装脚本会为三个数据子目录设置对应所有者并收紧为 `0700`。升级前请备份完整数据目录(包括 `.directus-secret`),并阅读目标版本的 breaking changes。Directus 会在启动时自动执行数据库迁移;迁移完成前不要中断容器。生产环境建议使用固定版本,`latest` 仅适合明确接受浮动更新的场景。
## 许可证与遥测
- Directus v12 使用 source-available 的 `MSCL-1.0-GPL`(Monospace Sustainable Core License 1.0),不是标准 OSI 开源许可证。
- 许可证禁止将软件用于与许可方商业 Directus 产品竞争的 **Competing Use**;使用者必须自行确认用途符合上游许可条款。
- 未配置许可证时实例运行在免费 `core tier`。更高限制或附加能力需要许可证;符合条件的组织可申请 Open Innovation Grant。
- 本包设置 `TELEMETRY=false` 和 `PROJECT_OWNER_ENABLED=false`。某些许可证权益可能要求遥测保持启用,上游授权规则优先。
- 若曾激活许可证,删除实例或清除数据库前应先在 Directus 中停用许可证,避免遗留占用激活名额。
## Introduction
Directus is a headless data platform that provides REST, GraphQL, and WebSocket APIs plus a data studio on top of a database. This package uses the official image in a single-service SQLite topology for single-node and small-to-medium self-hosted deployments.
The package persists `/directus/database`, `/directus/uploads`, and `/directus/extensions`, creates the initial administrator from installation fields, persists a generated token-signing secret in `.directus-secret`, and probes `/server/ping`. Larger or horizontally scaled deployments should use an external PostgreSQL/MySQL/MariaDB service and Redis where required.
Directus 12 is source-available under `MSCL-1.0-GPL`. Review the upstream license, including the Competing Use restriction and core-tier entitlements, before deployment.
## Features
- REST, GraphQL, and WebSocket APIs generated from the persisted SQLite database
- Directus Studio for schema, content, file, permission, and flow management
- Persistent local uploads and extension loading
## 参考资料
- Docker Compose Quickstart:<https://directus.io/docs/getting-started/create-a-project>
- 自托管部署:<https://directus.io/docs/self-hosting/deploying>
- 数据库配置:<https://directus.io/docs/configuration/database>
- 升级说明:<https://directus.io/docs/self-hosting/upgrading>
- 许可证说明:<https://directus.io/docs/licensing/overview>
- 许可证全文:<https://directus.com/license>
- 源码仓库:<https://github.com/directus/directus>
- 官方镜像:<https://hub.docker.com/r/directus/directus>
+32
View File
@@ -0,0 +1,32 @@
name: Directus
tags:
- Tool
title: 连接数据库并即时生成 API 与数据管理界面的无头数据平台
description: 连接数据库并即时生成 API 与数据管理界面的无头数据平台
additionalProperties:
key: directus
name: Directus
tags:
- Tool
shortDescZh: 连接数据库并即时生成 API 与数据管理界面的无头数据平台
shortDescEn: A headless data platform that connects to databases and instantly provides APIs and a data studio
description:
en: A headless data platform that connects to databases and instantly provides APIs and a data studio
zh: 连接数据库并即时生成 API 与数据管理界面的无头数据平台
zh-Hant: 連接資料庫並即時產生 API 與資料管理介面的無頭資料平台
ja: データベースに接続し、API とデータ管理画面を即座に提供するヘッドレスデータプラットフォーム
ko: 데이터베이스에 연결해 API와 데이터 관리 화면을 즉시 제공하는 헤드리스 데이터 플랫폼
ru: Headless-платформа данных, которая подключается к базам данных и сразу предоставляет API и интерфейс управления
ms: Platform data tanpa kepala yang bersambung ke pangkalan data serta menyediakan API dan studio data dengan segera
pt-br: Plataforma de dados headless que se conecta a bancos de dados e fornece APIs e um estúdio de dados instantaneamente
type: tool
crossVersionUpdate: true
limit: 0
recommend: 0
memoryRequired: 512
website: https://directus.io/
github: https://github.com/directus/directus
document: https://directus.io/docs/
architectures:
- amd64
- arm64
+7
View File
@@ -0,0 +1,7 @@
PANEL_APP_PORT_HTTP=8055
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=replace-with-a-strong-password
DIRECTUS_SECRET=replace-with-a-long-random-secret
PUBLIC_URL=/
APP_DATA_DIR=./data
CONTAINER_NAME=directus
+102
View File
@@ -0,0 +1,102 @@
additionalProperties:
formFields:
- default: 8055
edit: true
envKey: PANEL_APP_PORT_HTTP
labelEn: HTTP Port
labelZh: HTTP 端口
label:
en: HTTP Port
zh: HTTP 端口
zh-Hant: HTTP 連接埠
ja: HTTP ポート
ko: HTTP 포트
ru: Порт HTTP
ms: Port HTTP
pt-br: Porta HTTP
required: true
rule: paramPort
type: number
- default: admin@example.com
edit: true
envKey: ADMIN_EMAIL
labelEn: Administrator Email
labelZh: 管理员邮箱
label:
en: Administrator Email
zh: 管理员邮箱
zh-Hant: 管理員電子郵件
ja: 管理者メールアドレス
ko: 관리자 이메일
ru: Электронная почта администратора
ms: E-mel Pentadbir
pt-br: E-mail do administrador
required: true
type: text
- default: ""
edit: true
envKey: ADMIN_PASSWORD
labelEn: Administrator Password
labelZh: 管理员密码
label:
en: Administrator Password
zh: 管理员密码
zh-Hant: 管理員密碼
ja: 管理者パスワード
ko: 관리자 비밀번호
ru: Пароль администратора
ms: Kata Laluan Pentadbir
pt-br: Senha do administrador
required: true
rule: paramComplexity
type: password
- default: ""
edit: true
envKey: DIRECTUS_SECRET
labelEn: Instance Secret
labelZh: 实例密钥
label:
en: Instance Secret
zh: 实例密钥
zh-Hant: 執行個體密鑰
ja: インスタンスシークレット
ko: 인스턴스 비밀 키
ru: Секрет экземпляра
ms: Rahsia Instans
pt-br: Segredo da instância
random: true
required: true
rule: paramComplexity
type: password
- default: /
edit: true
envKey: PUBLIC_URL
labelEn: Public URL
labelZh: 公共访问地址
label:
en: Public URL
zh: 公共访问地址
zh-Hant: 公開存取位址
ja: 公開 URL
ko: 공개 URL
ru: Публичный URL
ms: URL Awam
pt-br: URL pública
required: false
type: text
- 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
View File
+35
View File
@@ -0,0 +1,35 @@
services:
directus:
image: "directus/directus:latest"
container_name: ${CONTAINER_NAME}
restart: unless-stopped
networks:
- 1panel-network
ports:
- "${PANEL_APP_PORT_HTTP}:8055"
environment:
- SECRET=${DIRECTUS_SECRET}
- DB_CLIENT=sqlite3
- DB_FILENAME=/directus/database/data.db
- ADMIN_EMAIL=${ADMIN_EMAIL}
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
- PUBLIC_URL=${PUBLIC_URL}
- WEBSOCKETS_ENABLED=true
- TELEMETRY=false
- PROJECT_OWNER_ENABLED=false
volumes:
- "${APP_DATA_DIR}/database:/directus/database"
- "${APP_DATA_DIR}/uploads:/directus/uploads"
- "${APP_DATA_DIR}/extensions:/directus/extensions"
healthcheck:
test: ["CMD-SHELL", "wget --spider -q http://127.0.0.1:8055/server/ping || exit 1"]
interval: 10s
timeout: 5s
start_period: 30s
retries: 10
labels:
createdBy: "Apps"
networks:
1panel-network:
external: true
+163
View File
@@ -0,0 +1,163 @@
#!/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"
}
read_env_value() {
local key="$1"
local value=""
if [[ -f "$ENV_FILE" ]]; then
value="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- || true)"
fi
strip_matching_quotes "$value"
}
path_is_dotenv_safe() {
local value="$1"
case "$value" in
*$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'"*) return 1 ;;
*) return 0 ;;
esac
}
secret_is_acceptable() {
local value="$1"
[[ ${#value} -ge 32 ]] || return 1
case "$value" in
*[!A-Za-z0-9._~!@%+=:,-]*) return 1 ;;
*) return 0 ;;
esac
}
generate_secret() {
local value
value="$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n')"
[[ "$value" =~ ^[0-9a-f]{64}$ ]] || fail "Unable to generate DIRECTUS_SECRET"
printf '%s\n' "$value"
}
write_env_value() {
local key="$1"
local value="$2"
local line
local found=0
local tmp_file
tmp_file="$(mktemp "${ENV_FILE}.tmp.XXXXXX")"
chmod 600 -- "$tmp_file"
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$line" == "${key}="* ]]; then
if [[ $found -eq 0 ]]; then
printf '%s=%s\n' "$key" "$value" >>"$tmp_file"
found=1
fi
else
printf '%s\n' "$line" >>"$tmp_file"
fi
done <"$ENV_FILE"
if [[ $found -eq 0 ]]; then
printf '%s=%s\n' "$key" "$value" >>"$tmp_file"
fi
mv -f -- "$tmp_file" "$ENV_FILE"
}
persist_secret() {
local value="$1"
local tmp_file
tmp_file="$(mktemp "${APP_DATA_DIR_ABS}/.directus-secret.tmp.XXXXXX")"
chmod 600 -- "$tmp_file"
printf '%s\n' "$value" >"$tmp_file"
mv -f -- "$tmp_file" "$SECRET_FILE"
}
[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found"
[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link"
if [[ ${APP_DATA_DIR+x} ]]; then
APP_DATA_DIR_RAW="$APP_DATA_DIR"
else
APP_DATA_DIR_RAW="$(read_env_value APP_DATA_DIR)"
fi
APP_DATA_DIR_RAW="$(strip_matching_quotes "${APP_DATA_DIR_RAW:-./data}")"
[[ -n "$APP_DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be empty"
path_is_dotenv_safe "$APP_DATA_DIR_RAW" || fail "APP_DATA_DIR contains unsupported dotenv characters"
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"
[[ ! -L "$APP_DATA_DIR_ABS" ]] || fail "APP_DATA_DIR must not be a symbolic link"
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"
for directory in database uploads extensions; do
target="${APP_DATA_DIR_ABS}/${directory}"
[[ ! -L "$target" ]] || fail "$target must not be a symbolic link"
if [[ -e "$target" && ! -d "$target" ]]; then
fail "$target must be a directory"
fi
mkdir -p -- "$target"
chown 1000:1000 -- "$target"
chmod 700 -- "$target"
done
SECRET_FILE="${APP_DATA_DIR_ABS}/.directus-secret"
[[ ! -L "$SECRET_FILE" ]] || fail "$SECRET_FILE must not be a symbolic link"
if [[ -e "$SECRET_FILE" ]]; then
[[ -f "$SECRET_FILE" ]] || fail "$SECRET_FILE must be a regular file"
DIRECTUS_SECRET_FINAL="$(<"$SECRET_FILE")"
secret_is_acceptable "$DIRECTUS_SECRET_FINAL" || fail "$SECRET_FILE contains an invalid secret"
else
if [[ ${DIRECTUS_SECRET+x} ]]; then
DIRECTUS_SECRET_CANDIDATE="$DIRECTUS_SECRET"
else
DIRECTUS_SECRET_CANDIDATE="$(read_env_value DIRECTUS_SECRET)"
fi
if secret_is_acceptable "$DIRECTUS_SECRET_CANDIDATE"; then
DIRECTUS_SECRET_FINAL="$DIRECTUS_SECRET_CANDIDATE"
else
DIRECTUS_SECRET_FINAL="$(generate_secret)"
fi
persist_secret "$DIRECTUS_SECRET_FINAL"
fi
chmod 600 -- "$SECRET_FILE"
write_env_value DIRECTUS_SECRET "$DIRECTUS_SECRET_FINAL"
+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: 2.4 KiB