From ded145e48eef76cfa9c597adbdafe9cfca069b3b Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Mon, 31 Aug 2026 17:02:33 +0800 Subject: [PATCH] Update sqlpage app Rebuilt-from-PR: #6309 Source-PR: https://github.com/okxlin/appstore/pull/6309 Maintainer-workflow: appstore-pr-maintainer --- apps/sqlpage/0.45.0/data.yml | 134 ---------------- apps/sqlpage/0.45.0/docker-compose.yml | 36 ----- apps/sqlpage/0.45.0/scripts/init.sh | 104 ------------ apps/sqlpage/{0.45.0 => 0.46.0}/.env.sample | 2 +- .../config/migrations/0000_init.sql | 0 .../{0.45.0 => 0.46.0}/config/sqlpage.json | 0 apps/sqlpage/0.46.0/data.yml | 150 ++++++++++++++++++ .../{latest => 0.46.0}/docker-compose.yml | 2 +- apps/sqlpage/0.46.0/scripts/init.sh | 94 +++++++++++ .../{0.45.0 => 0.46.0}/scripts/uninstall.sh | 0 .../{0.45.0 => 0.46.0}/scripts/upgrade.sh | 0 .../site-template/delete.sql | 0 .../site-template/index.sql | 0 .../site-template/shell.sql | 0 .../site-template/timeline.sql | 0 .../site-template/todo_form.sql | 0 apps/sqlpage/data.yml | 25 ++- apps/sqlpage/latest/.env.sample | 8 - .../latest/config/migrations/0000_init.sql | 5 - apps/sqlpage/latest/config/sqlpage.json | 3 - apps/sqlpage/latest/data.yml | 134 ---------------- apps/sqlpage/latest/scripts/init.sh | 104 ------------ apps/sqlpage/latest/scripts/uninstall.sh | 4 - apps/sqlpage/latest/scripts/upgrade.sh | 5 - apps/sqlpage/latest/site-template/delete.sql | 16 -- apps/sqlpage/latest/site-template/index.sql | 20 --- apps/sqlpage/latest/site-template/shell.sql | 4 - .../sqlpage/latest/site-template/timeline.sql | 12 -- .../latest/site-template/todo_form.sql | 22 --- apps/sqlpage/logo.png | Bin 9131 -> 9090 bytes 30 files changed, 258 insertions(+), 626 deletions(-) delete mode 100644 apps/sqlpage/0.45.0/data.yml delete mode 100644 apps/sqlpage/0.45.0/docker-compose.yml delete mode 100755 apps/sqlpage/0.45.0/scripts/init.sh rename apps/sqlpage/{0.45.0 => 0.46.0}/.env.sample (84%) rename apps/sqlpage/{0.45.0 => 0.46.0}/config/migrations/0000_init.sql (100%) rename apps/sqlpage/{0.45.0 => 0.46.0}/config/sqlpage.json (100%) create mode 100644 apps/sqlpage/0.46.0/data.yml rename apps/sqlpage/{latest => 0.46.0}/docker-compose.yml (96%) create mode 100755 apps/sqlpage/0.46.0/scripts/init.sh rename apps/sqlpage/{0.45.0 => 0.46.0}/scripts/uninstall.sh (100%) rename apps/sqlpage/{0.45.0 => 0.46.0}/scripts/upgrade.sh (100%) rename apps/sqlpage/{0.45.0 => 0.46.0}/site-template/delete.sql (100%) rename apps/sqlpage/{0.45.0 => 0.46.0}/site-template/index.sql (100%) rename apps/sqlpage/{0.45.0 => 0.46.0}/site-template/shell.sql (100%) rename apps/sqlpage/{0.45.0 => 0.46.0}/site-template/timeline.sql (100%) rename apps/sqlpage/{0.45.0 => 0.46.0}/site-template/todo_form.sql (100%) delete mode 100644 apps/sqlpage/latest/.env.sample delete mode 100644 apps/sqlpage/latest/config/migrations/0000_init.sql delete mode 100644 apps/sqlpage/latest/config/sqlpage.json delete mode 100644 apps/sqlpage/latest/data.yml delete mode 100755 apps/sqlpage/latest/scripts/init.sh delete mode 100755 apps/sqlpage/latest/scripts/uninstall.sh delete mode 100755 apps/sqlpage/latest/scripts/upgrade.sh delete mode 100644 apps/sqlpage/latest/site-template/delete.sql delete mode 100644 apps/sqlpage/latest/site-template/index.sql delete mode 100644 apps/sqlpage/latest/site-template/shell.sql delete mode 100644 apps/sqlpage/latest/site-template/timeline.sql delete mode 100644 apps/sqlpage/latest/site-template/todo_form.sql diff --git a/apps/sqlpage/0.45.0/data.yml b/apps/sqlpage/0.45.0/data.yml deleted file mode 100644 index 93c75b405..000000000 --- a/apps/sqlpage/0.45.0/data.yml +++ /dev/null @@ -1,134 +0,0 @@ -additionalProperties: - formFields: - - default: 127.0.0.1 - edit: true - envKey: PANEL_APP_BIND_ADDRESS - labelEn: Bind Address - labelZh: 绑定地址 - label: - en: Bind Address - zh: 绑定地址 - zh-Hant: 綁定位址 - ja: バインドアドレス - ko: 바인드 주소 - ru: Адрес привязки - ms: Alamat ikatan - pt-br: Endereco de vinculacao - required: true - type: text - - default: 8080 - 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: production - edit: true - envKey: SQLPAGE_ENVIRONMENT - labelEn: Runtime Environment - labelZh: 运行环境 - label: - en: Runtime Environment - zh: 运行环境 - zh-Hant: 執行環境 - ja: 実行環境 - ko: 실행 환경 - ru: Среда выполнения - ms: Persekitaran masa jalan - pt-br: Ambiente de execucao - required: true - type: select - values: - - label: production - value: production - - label: development - value: development - - default: sqlpage=info - edit: true - envKey: LOG_LEVEL - labelEn: Log Level - labelZh: 日志级别 - label: - en: Log Level - zh: 日志级别 - zh-Hant: 日誌級別 - ja: ログレベル - ko: 로그 수준 - ru: Уровень журнала - ms: Tahap log - pt-br: Nivel de log - required: true - type: select - values: - - label: error - value: sqlpage=error - - label: warn - value: sqlpage=warn - - label: info - value: sqlpage=info - - label: debug - value: sqlpage=debug - - default: "false" - edit: true - envKey: SQLPAGE_COMPRESS_RESPONSES - labelEn: Compress Responses - labelZh: 压缩响应 - label: - en: Compress Responses - zh: 压缩响应 - zh-Hant: 壓縮回應 - ja: レスポンスを圧縮 - ko: 응답 압축 - ru: Сжатие ответов - ms: Mampatkan respons - pt-br: Compactar respostas - required: true - type: select - values: - - label: "false" - value: "false" - - label: "true" - value: "true" - - default: 5242880 - edit: true - envKey: SQLPAGE_MAX_UPLOADED_FILE_SIZE - labelEn: Maximum Upload Size (bytes) - labelZh: 最大上传大小(字节) - label: - en: Maximum Upload Size (bytes) - zh: 最大上传大小(字节) - zh-Hant: 最大上傳大小(位元組) - ja: 最大アップロードサイズ(バイト) - ko: 최대 업로드 크기(바이트) - ru: Максимальный размер загрузки (байт) - ms: Saiz muat naik maksimum (bait) - pt-br: Tamanho maximo de upload (bytes) - required: true - type: number - - default: ./data - edit: true - envKey: APP_DATA_DIR - labelEn: Website Data Directory - labelZh: 网站数据目录 - label: - en: Website Data Directory - zh: 网站数据目录 - zh-Hant: 網站資料目錄 - ja: Web サイトデータディレクトリ - ko: 웹사이트 데이터 디렉터리 - ru: Каталог данных сайта - ms: Direktori data laman web - pt-br: Diretorio de dados do site - required: true - type: text diff --git a/apps/sqlpage/0.45.0/docker-compose.yml b/apps/sqlpage/0.45.0/docker-compose.yml deleted file mode 100644 index 155b89b14..000000000 --- a/apps/sqlpage/0.45.0/docker-compose.yml +++ /dev/null @@ -1,36 +0,0 @@ -services: - sqlpage: - image: "lovasoa/sqlpage:v0.45.0" - container_name: ${CONTAINER_NAME} - restart: unless-stopped - user: "1000:1000" - networks: - - 1panel-network - ports: - - "${PANEL_APP_BIND_ADDRESS}:${PANEL_APP_PORT_HTTP}:8080" - environment: - - SQLPAGE_WEB_ROOT=/var/www - - SQLPAGE_CONFIGURATION_DIRECTORY=/etc/sqlpage - - SQLPAGE_ENVIRONMENT=${SQLPAGE_ENVIRONMENT} - - LOG_LEVEL=${LOG_LEVEL} - - SQLPAGE_COMPRESS_RESPONSES=${SQLPAGE_COMPRESS_RESPONSES} - - SQLPAGE_MAX_UPLOADED_FILE_SIZE=${SQLPAGE_MAX_UPLOADED_FILE_SIZE} - - SQLPAGE_ALLOW_EXEC=false - - SQLPAGE_MARKDOWN_ALLOW_DANGEROUS_HTML=false - - SQLPAGE_MARKDOWN_ALLOW_DANGEROUS_PROTOCOL=false - volumes: - - "${APP_DATA_DIR}:/var/www" - - "./config:/etc/sqlpage:ro" - read_only: true - tmpfs: - - /tmp:rw,nosuid,nodev,noexec,size=32m,uid=1000,gid=1000,mode=1777 - cap_drop: - - ALL - security_opt: - - no-new-privileges:true - labels: - createdBy: "Apps" - -networks: - 1panel-network: - external: true diff --git a/apps/sqlpage/0.45.0/scripts/init.sh b/apps/sqlpage/0.45.0/scripts/init.sh deleted file mode 100755 index 79e38218b..000000000 --- a/apps/sqlpage/0.45.0/scripts/init.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" -ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}" -TEMPLATE_DIR="${ROOT_DIR}/site-template" - -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 - value="${value%$'\r'}" - strip_matching_quotes "$value" -} - -path_is_dotenv_safe() { - local value="$1" - - case "$value" in - *$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'"*) return 1 ;; - *) return 0 ;; - esac -} - -[[ -d "$TEMPLATE_DIR" ]] || fail "Starter template directory not found: ${TEMPLATE_DIR}" - -if [[ ${APP_DATA_DIR+x} ]]; then - DATA_DIR_RAW="$APP_DATA_DIR" -else - DATA_DIR_RAW="$(read_env_value APP_DATA_DIR)" -fi -DATA_DIR_RAW="$(strip_matching_quotes "${DATA_DIR_RAW:-./data}")" - -[[ -n "$DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be empty" -path_is_dotenv_safe "$DATA_DIR_RAW" || fail "APP_DATA_DIR contains unsupported dotenv characters" - -if [[ "$DATA_DIR_RAW" = /* ]]; then - [[ ! -L "$DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be a symbolic link" - DATA_DIR_ABS="$(realpath -m -- "$DATA_DIR_RAW")" - [[ "$DATA_DIR_ABS" != "/" ]] || fail "APP_DATA_DIR must not be the filesystem root" - - if [[ -e "$DATA_DIR_ABS" ]]; then - [[ -d "$DATA_DIR_ABS" ]] || fail "APP_DATA_DIR must be a directory" - [[ "$(stat -c '%u:%g' "$DATA_DIR_ABS")" == "1000:1000" ]] || \ - fail "Existing absolute APP_DATA_DIR must be owned by UID/GID 1000:1000" - else - install -d -m 0750 -- "$DATA_DIR_ABS" - if [[ "$(id -u)" -eq 0 ]]; then - chown 1000:1000 -- "$DATA_DIR_ABS" - fi - fi -else - DATA_DIR_PATH="${ROOT_DIR}/${DATA_DIR_RAW#./}" - [[ ! -L "$DATA_DIR_PATH" ]] || fail "APP_DATA_DIR must not be a symbolic link" - DATA_DIR_ABS="$(realpath -m -- "$DATA_DIR_PATH")" - case "$DATA_DIR_ABS" in - "${ROOT_DIR}"/*) ;; - *) fail "Relative APP_DATA_DIR must remain inside the application version directory" ;; - esac - if [[ -e "$DATA_DIR_ABS" && ! -d "$DATA_DIR_ABS" ]]; then - fail "APP_DATA_DIR must be a directory" - fi - install -d -m 0750 -- "$DATA_DIR_ABS" - if [[ "$(id -u)" -eq 0 ]]; then - chown 1000:1000 -- "$DATA_DIR_ABS" - fi -fi - -[[ "$(stat -c '%u:%g' "$DATA_DIR_ABS")" == "1000:1000" ]] || \ - fail "APP_DATA_DIR must be owned by UID/GID 1000:1000" - -for source_file in "${TEMPLATE_DIR}"/*.sql; do - target_file="${DATA_DIR_ABS}/${source_file##*/}" - [[ ! -L "$target_file" ]] || fail "Starter target must not be a symbolic link: ${target_file}" - if [[ -e "$target_file" ]]; then - [[ -f "$target_file" ]] || fail "Starter target must be a regular file: ${target_file}" - continue - fi - install -m 0644 -- "$source_file" "$target_file" - if [[ "$(id -u)" -eq 0 ]]; then - chown 1000:1000 -- "$target_file" - fi -done diff --git a/apps/sqlpage/0.45.0/.env.sample b/apps/sqlpage/0.46.0/.env.sample similarity index 84% rename from apps/sqlpage/0.45.0/.env.sample rename to apps/sqlpage/0.46.0/.env.sample index 396f68188..3952505c0 100644 --- a/apps/sqlpage/0.45.0/.env.sample +++ b/apps/sqlpage/0.46.0/.env.sample @@ -1,4 +1,4 @@ -CONTAINER_NAME=sqlpage +CONTAINER_NAME=sqlpage-compose-check PANEL_APP_BIND_ADDRESS=127.0.0.1 PANEL_APP_PORT_HTTP=8080 SQLPAGE_ENVIRONMENT=production diff --git a/apps/sqlpage/0.45.0/config/migrations/0000_init.sql b/apps/sqlpage/0.46.0/config/migrations/0000_init.sql similarity index 100% rename from apps/sqlpage/0.45.0/config/migrations/0000_init.sql rename to apps/sqlpage/0.46.0/config/migrations/0000_init.sql diff --git a/apps/sqlpage/0.45.0/config/sqlpage.json b/apps/sqlpage/0.46.0/config/sqlpage.json similarity index 100% rename from apps/sqlpage/0.45.0/config/sqlpage.json rename to apps/sqlpage/0.46.0/config/sqlpage.json diff --git a/apps/sqlpage/0.46.0/data.yml b/apps/sqlpage/0.46.0/data.yml new file mode 100644 index 000000000..a952131a8 --- /dev/null +++ b/apps/sqlpage/0.46.0/data.yml @@ -0,0 +1,150 @@ +additionalProperties: + formFields: + - default: 127.0.0.1 + edit: true + envKey: PANEL_APP_BIND_ADDRESS + labelEn: Bind Address + labelZh: 绑定地址 + label: + en: Bind Address + zh: 绑定地址 + zh-Hant: 綁定位址 + ja: バインドアドレス + ko: 바인드 주소 + ru: Адрес привязки + ms: Alamat ikatan + pt-br: Endereco de vinculacao + required: true + type: text + - default: 8080 + 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: production + edit: true + envKey: SQLPAGE_ENVIRONMENT + labelEn: Runtime Environment + labelZh: 运行环境 + label: + en: Runtime Environment + zh: 运行环境 + zh-Hant: 執行環境 + ja: 実行環境 + ko: 실행 환경 + ru: Среда выполнения + ms: Persekitaran masa jalan + pt-br: Ambiente de execucao + required: true + type: select + values: + - label: production + value: production + - label: development + value: development + - default: sqlpage=info + edit: true + envKey: LOG_LEVEL + labelEn: Log Level + labelZh: 日志级别 + label: + en: Log Level + zh: 日志级别 + zh-Hant: 日誌級別 + ja: ログレベル + ko: 로그 수준 + ru: Уровень журнала + ms: Tahap log + pt-br: Nivel de log + required: true + type: select + values: + - label: error + value: sqlpage=error + - label: warn + value: sqlpage=warn + - label: info + value: sqlpage=info + - label: debug + value: sqlpage=debug + - default: 'false' + edit: true + envKey: SQLPAGE_COMPRESS_RESPONSES + labelEn: Compress Responses + labelZh: 压缩响应 + label: + en: Compress Responses + zh: 压缩响应 + zh-Hant: 壓縮回應 + ja: レスポンスを圧縮 + ko: 응답 압축 + ru: Сжатие ответов + ms: Mampatkan respons + pt-br: Compactar respostas + required: true + type: select + values: + - label: 'false' + value: 'false' + - label: 'true' + value: 'true' + - default: 5242880 + edit: true + envKey: SQLPAGE_MAX_UPLOADED_FILE_SIZE + labelEn: Maximum Upload Size (bytes) + labelZh: 最大上传大小(字节) + label: + en: Maximum Upload Size (bytes) + zh: 最大上传大小(字节) + zh-Hant: 最大上傳大小(位元組) + ja: 最大アップロードサイズ(バイト) + ko: 최대 업로드 크기(바이트) + ru: Максимальный размер загрузки (байт) + ms: Saiz muat naik maksimum (bait) + pt-br: Tamanho maximo de upload (bytes) + required: true + type: number + - default: ./data + edit: true + envKey: APP_DATA_DIR + labelEn: Website Data Directory + labelZh: 网站数据目录 + label: + en: Website Data Directory + zh: 网站数据目录 + zh-Hant: 網站資料目錄 + ja: Web サイトデータディレクトリ + ko: 웹사이트 데이터 디렉터리 + ru: Каталог данных сайта + ms: Direktori data laman web + pt-br: Diretorio de dados do site + required: true + type: text + - default: Asia/Shanghai + edit: true + envKey: TZ + labelEn: Timezone + labelZh: 时区 + label: + en: Timezone + zh: 时区 + zh-Hant: 時區 + ja: タイムゾーン + ko: 시간대 + ru: Часовой пояс + ms: Zon waktu + pt-br: Fuso horário + required: true + type: text diff --git a/apps/sqlpage/latest/docker-compose.yml b/apps/sqlpage/0.46.0/docker-compose.yml similarity index 96% rename from apps/sqlpage/latest/docker-compose.yml rename to apps/sqlpage/0.46.0/docker-compose.yml index 879b24e7e..0132cd512 100644 --- a/apps/sqlpage/latest/docker-compose.yml +++ b/apps/sqlpage/0.46.0/docker-compose.yml @@ -1,6 +1,6 @@ services: sqlpage: - image: "lovasoa/sqlpage:latest" + image: "lovasoa/sqlpage:v0.46.0" container_name: ${CONTAINER_NAME} restart: unless-stopped user: "1000:1000" diff --git a/apps/sqlpage/0.46.0/scripts/init.sh b/apps/sqlpage/0.46.0/scripts/init.sh new file mode 100755 index 000000000..3bc3bf5e5 --- /dev/null +++ b/apps/sqlpage/0.46.0/scripts/init.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -euo pipefail +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +export PATH + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}" +TEMPLATE_DIR="${ROOT_DIR}/site-template" + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +read_env_value() { + local key="$1" + [[ -f "$ENV_FILE" ]] || return 0 + local value + value="$(sed -n "s/^${key}=//p" "$ENV_FILE" | tail -n 1)" + case "$value" in + \"*\") value="${value#\"}"; value="${value%\"}" ;; + \'*\') value="${value#\'}"; value="${value%\'}" ;; + esac + printf '%s\n' "$value" +} + +configured_value() { + local key="$1" + local default_value="$2" + local value + value="${!key:-}" + if [[ -z "$value" ]]; then + value="$(read_env_value "$key")" + fi + printf '%s\n' "${value:-$default_value}" +} + +resolve_app_path() { + local key="$1" + local raw="$2" + local clean candidate resolved current part + local -a parts=() + case "$raw" in + ""|/*|.|..|../*|*/../*|*/..) echo "unsafe ${key} path" >&2; return 1 ;; + esac + if [[ "$raw" =~ [[:cntrl:]] ]]; then + echo "unsafe ${key} path" >&2 + return 1 + fi + clean="${raw#./}" + [[ -n "$clean" ]] || { echo "unsafe ${key} path" >&2; return 1; } + command -v realpath >/dev/null 2>&1 || { echo "realpath is required" >&2; return 1; } + candidate="$ROOT_DIR/$clean" + resolved="$(realpath -m -- "$candidate")" || { echo "unsafe ${key} path" >&2; return 1; } + case "$resolved" in + "$ROOT_DIR"/*) ;; + *) echo "unsafe ${key} path" >&2; return 1 ;; + esac + current="$ROOT_DIR" + IFS='/' read -r -a parts <<< "$clean" + for part in "${parts[@]}"; do + [[ -z "$part" || "$part" == "." ]] && continue + current="$current/$part" + if [[ -L "$current" ]]; then + echo "unsafe ${key} path" >&2 + return 1 + fi + done + printf '%s\n' "$resolved" +} + +[[ -d "$TEMPLATE_DIR" ]] || fail "Starter template directory not found: ${TEMPLATE_DIR}" + +DATA_DIR_RAW="$(configured_value APP_DATA_DIR ./data)" +DATA_DIR_ABS="$(resolve_app_path APP_DATA_DIR "$DATA_DIR_RAW")" +install -d -m 0750 -- "$DATA_DIR_ABS" +if [[ "$(id -u)" -eq 0 ]]; then + chown 1000:1000 -- "$DATA_DIR_ABS" +fi +[[ "$(stat -c '%u:%g' "$DATA_DIR_ABS")" == "1000:1000" ]] || \ + fail "APP_DATA_DIR must be owned by UID/GID 1000:1000" + +for source_file in "${TEMPLATE_DIR}"/*.sql; do + target_file="${DATA_DIR_ABS}/${source_file##*/}" + [[ ! -L "$target_file" ]] || fail "Starter target must not be a symbolic link: ${target_file}" + if [[ -e "$target_file" ]]; then + [[ -f "$target_file" ]] || fail "Starter target must be a regular file: ${target_file}" + continue + fi + install -m 0644 -- "$source_file" "$target_file" + if [[ "$(id -u)" -eq 0 ]]; then + chown 1000:1000 -- "$target_file" + fi +done diff --git a/apps/sqlpage/0.45.0/scripts/uninstall.sh b/apps/sqlpage/0.46.0/scripts/uninstall.sh similarity index 100% rename from apps/sqlpage/0.45.0/scripts/uninstall.sh rename to apps/sqlpage/0.46.0/scripts/uninstall.sh diff --git a/apps/sqlpage/0.45.0/scripts/upgrade.sh b/apps/sqlpage/0.46.0/scripts/upgrade.sh similarity index 100% rename from apps/sqlpage/0.45.0/scripts/upgrade.sh rename to apps/sqlpage/0.46.0/scripts/upgrade.sh diff --git a/apps/sqlpage/0.45.0/site-template/delete.sql b/apps/sqlpage/0.46.0/site-template/delete.sql similarity index 100% rename from apps/sqlpage/0.45.0/site-template/delete.sql rename to apps/sqlpage/0.46.0/site-template/delete.sql diff --git a/apps/sqlpage/0.45.0/site-template/index.sql b/apps/sqlpage/0.46.0/site-template/index.sql similarity index 100% rename from apps/sqlpage/0.45.0/site-template/index.sql rename to apps/sqlpage/0.46.0/site-template/index.sql diff --git a/apps/sqlpage/0.45.0/site-template/shell.sql b/apps/sqlpage/0.46.0/site-template/shell.sql similarity index 100% rename from apps/sqlpage/0.45.0/site-template/shell.sql rename to apps/sqlpage/0.46.0/site-template/shell.sql diff --git a/apps/sqlpage/0.45.0/site-template/timeline.sql b/apps/sqlpage/0.46.0/site-template/timeline.sql similarity index 100% rename from apps/sqlpage/0.45.0/site-template/timeline.sql rename to apps/sqlpage/0.46.0/site-template/timeline.sql diff --git a/apps/sqlpage/0.45.0/site-template/todo_form.sql b/apps/sqlpage/0.46.0/site-template/todo_form.sql similarity index 100% rename from apps/sqlpage/0.45.0/site-template/todo_form.sql rename to apps/sqlpage/0.46.0/site-template/todo_form.sql diff --git a/apps/sqlpage/data.yml b/apps/sqlpage/data.yml index f8cb5db0c..41eb1a37e 100644 --- a/apps/sqlpage/data.yml +++ b/apps/sqlpage/data.yml @@ -1,15 +1,25 @@ name: SQLPage tags: - - 工具 +- Tool title: 完全由 SQL 驱动的动态网站构建器 description: 完全由 SQL 驱动的动态网站构建器 additionalProperties: key: sqlpage name: SQLPage tags: - - Tool + - Tool + type: website + website: https://sql-page.com + document: https://sql-page.com/documentation.sql + github: https://github.com/sqlpage/SQLPage shortDescZh: 完全由 SQL 驱动的动态网站构建器 shortDescEn: A dynamic website builder powered entirely by SQL + crossVersionUpdate: true + limit: 0 + architectures: + - amd64 + - arm64 + - armv7 description: en: A dynamic website builder powered entirely by SQL zh: 完全由 SQL 驱动的动态网站构建器 @@ -19,14 +29,3 @@ additionalProperties: ru: Конструктор динамических сайтов, полностью управляемый SQL ms: Pembina laman web dinamik yang dikuasakan sepenuhnya oleh SQL pt-br: Construtor de sites dinamicos totalmente orientado por SQL - type: website - crossVersionUpdate: true - limit: 0 - recommend: 0 - website: https://sql-page.com - github: https://github.com/sqlpage/SQLPage - document: https://sql-page.com/documentation.sql - architectures: - - amd64 - - arm64 - - armv7 diff --git a/apps/sqlpage/latest/.env.sample b/apps/sqlpage/latest/.env.sample deleted file mode 100644 index 396f68188..000000000 --- a/apps/sqlpage/latest/.env.sample +++ /dev/null @@ -1,8 +0,0 @@ -CONTAINER_NAME=sqlpage -PANEL_APP_BIND_ADDRESS=127.0.0.1 -PANEL_APP_PORT_HTTP=8080 -SQLPAGE_ENVIRONMENT=production -LOG_LEVEL=sqlpage=info -SQLPAGE_COMPRESS_RESPONSES=false -SQLPAGE_MAX_UPLOADED_FILE_SIZE=5242880 -APP_DATA_DIR=./data diff --git a/apps/sqlpage/latest/config/migrations/0000_init.sql b/apps/sqlpage/latest/config/migrations/0000_init.sql deleted file mode 100644 index f9d638a85..000000000 --- a/apps/sqlpage/latest/config/migrations/0000_init.sql +++ /dev/null @@ -1,5 +0,0 @@ -create table todos( - id integer primary key, - title text not null, - created_at timestamp default current_timestamp -); diff --git a/apps/sqlpage/latest/config/sqlpage.json b/apps/sqlpage/latest/config/sqlpage.json deleted file mode 100644 index d94aa8f8c..000000000 --- a/apps/sqlpage/latest/config/sqlpage.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "database_url": "sqlite:///var/www/sqlpage.db?mode=rwc" -} diff --git a/apps/sqlpage/latest/data.yml b/apps/sqlpage/latest/data.yml deleted file mode 100644 index 93c75b405..000000000 --- a/apps/sqlpage/latest/data.yml +++ /dev/null @@ -1,134 +0,0 @@ -additionalProperties: - formFields: - - default: 127.0.0.1 - edit: true - envKey: PANEL_APP_BIND_ADDRESS - labelEn: Bind Address - labelZh: 绑定地址 - label: - en: Bind Address - zh: 绑定地址 - zh-Hant: 綁定位址 - ja: バインドアドレス - ko: 바인드 주소 - ru: Адрес привязки - ms: Alamat ikatan - pt-br: Endereco de vinculacao - required: true - type: text - - default: 8080 - 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: production - edit: true - envKey: SQLPAGE_ENVIRONMENT - labelEn: Runtime Environment - labelZh: 运行环境 - label: - en: Runtime Environment - zh: 运行环境 - zh-Hant: 執行環境 - ja: 実行環境 - ko: 실행 환경 - ru: Среда выполнения - ms: Persekitaran masa jalan - pt-br: Ambiente de execucao - required: true - type: select - values: - - label: production - value: production - - label: development - value: development - - default: sqlpage=info - edit: true - envKey: LOG_LEVEL - labelEn: Log Level - labelZh: 日志级别 - label: - en: Log Level - zh: 日志级别 - zh-Hant: 日誌級別 - ja: ログレベル - ko: 로그 수준 - ru: Уровень журнала - ms: Tahap log - pt-br: Nivel de log - required: true - type: select - values: - - label: error - value: sqlpage=error - - label: warn - value: sqlpage=warn - - label: info - value: sqlpage=info - - label: debug - value: sqlpage=debug - - default: "false" - edit: true - envKey: SQLPAGE_COMPRESS_RESPONSES - labelEn: Compress Responses - labelZh: 压缩响应 - label: - en: Compress Responses - zh: 压缩响应 - zh-Hant: 壓縮回應 - ja: レスポンスを圧縮 - ko: 응답 압축 - ru: Сжатие ответов - ms: Mampatkan respons - pt-br: Compactar respostas - required: true - type: select - values: - - label: "false" - value: "false" - - label: "true" - value: "true" - - default: 5242880 - edit: true - envKey: SQLPAGE_MAX_UPLOADED_FILE_SIZE - labelEn: Maximum Upload Size (bytes) - labelZh: 最大上传大小(字节) - label: - en: Maximum Upload Size (bytes) - zh: 最大上传大小(字节) - zh-Hant: 最大上傳大小(位元組) - ja: 最大アップロードサイズ(バイト) - ko: 최대 업로드 크기(바이트) - ru: Максимальный размер загрузки (байт) - ms: Saiz muat naik maksimum (bait) - pt-br: Tamanho maximo de upload (bytes) - required: true - type: number - - default: ./data - edit: true - envKey: APP_DATA_DIR - labelEn: Website Data Directory - labelZh: 网站数据目录 - label: - en: Website Data Directory - zh: 网站数据目录 - zh-Hant: 網站資料目錄 - ja: Web サイトデータディレクトリ - ko: 웹사이트 데이터 디렉터리 - ru: Каталог данных сайта - ms: Direktori data laman web - pt-br: Diretorio de dados do site - required: true - type: text diff --git a/apps/sqlpage/latest/scripts/init.sh b/apps/sqlpage/latest/scripts/init.sh deleted file mode 100755 index 79e38218b..000000000 --- a/apps/sqlpage/latest/scripts/init.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" -ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}" -TEMPLATE_DIR="${ROOT_DIR}/site-template" - -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 - value="${value%$'\r'}" - strip_matching_quotes "$value" -} - -path_is_dotenv_safe() { - local value="$1" - - case "$value" in - *$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'"*) return 1 ;; - *) return 0 ;; - esac -} - -[[ -d "$TEMPLATE_DIR" ]] || fail "Starter template directory not found: ${TEMPLATE_DIR}" - -if [[ ${APP_DATA_DIR+x} ]]; then - DATA_DIR_RAW="$APP_DATA_DIR" -else - DATA_DIR_RAW="$(read_env_value APP_DATA_DIR)" -fi -DATA_DIR_RAW="$(strip_matching_quotes "${DATA_DIR_RAW:-./data}")" - -[[ -n "$DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be empty" -path_is_dotenv_safe "$DATA_DIR_RAW" || fail "APP_DATA_DIR contains unsupported dotenv characters" - -if [[ "$DATA_DIR_RAW" = /* ]]; then - [[ ! -L "$DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be a symbolic link" - DATA_DIR_ABS="$(realpath -m -- "$DATA_DIR_RAW")" - [[ "$DATA_DIR_ABS" != "/" ]] || fail "APP_DATA_DIR must not be the filesystem root" - - if [[ -e "$DATA_DIR_ABS" ]]; then - [[ -d "$DATA_DIR_ABS" ]] || fail "APP_DATA_DIR must be a directory" - [[ "$(stat -c '%u:%g' "$DATA_DIR_ABS")" == "1000:1000" ]] || \ - fail "Existing absolute APP_DATA_DIR must be owned by UID/GID 1000:1000" - else - install -d -m 0750 -- "$DATA_DIR_ABS" - if [[ "$(id -u)" -eq 0 ]]; then - chown 1000:1000 -- "$DATA_DIR_ABS" - fi - fi -else - DATA_DIR_PATH="${ROOT_DIR}/${DATA_DIR_RAW#./}" - [[ ! -L "$DATA_DIR_PATH" ]] || fail "APP_DATA_DIR must not be a symbolic link" - DATA_DIR_ABS="$(realpath -m -- "$DATA_DIR_PATH")" - case "$DATA_DIR_ABS" in - "${ROOT_DIR}"/*) ;; - *) fail "Relative APP_DATA_DIR must remain inside the application version directory" ;; - esac - if [[ -e "$DATA_DIR_ABS" && ! -d "$DATA_DIR_ABS" ]]; then - fail "APP_DATA_DIR must be a directory" - fi - install -d -m 0750 -- "$DATA_DIR_ABS" - if [[ "$(id -u)" -eq 0 ]]; then - chown 1000:1000 -- "$DATA_DIR_ABS" - fi -fi - -[[ "$(stat -c '%u:%g' "$DATA_DIR_ABS")" == "1000:1000" ]] || \ - fail "APP_DATA_DIR must be owned by UID/GID 1000:1000" - -for source_file in "${TEMPLATE_DIR}"/*.sql; do - target_file="${DATA_DIR_ABS}/${source_file##*/}" - [[ ! -L "$target_file" ]] || fail "Starter target must not be a symbolic link: ${target_file}" - if [[ -e "$target_file" ]]; then - [[ -f "$target_file" ]] || fail "Starter target must be a regular file: ${target_file}" - continue - fi - install -m 0644 -- "$source_file" "$target_file" - if [[ "$(id -u)" -eq 0 ]]; then - chown 1000:1000 -- "$target_file" - fi -done diff --git a/apps/sqlpage/latest/scripts/uninstall.sh b/apps/sqlpage/latest/scripts/uninstall.sh deleted file mode 100755 index c6af20129..000000000 --- a/apps/sqlpage/latest/scripts/uninstall.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -docker-compose down --volumes --remove-orphans diff --git a/apps/sqlpage/latest/scripts/upgrade.sh b/apps/sqlpage/latest/scripts/upgrade.sh deleted file mode 100755 index e73ce2436..000000000 --- a/apps/sqlpage/latest/scripts/upgrade.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" -exec "${SCRIPT_DIR}/init.sh" diff --git a/apps/sqlpage/latest/site-template/delete.sql b/apps/sqlpage/latest/site-template/delete.sql deleted file mode 100644 index 051348e2d..000000000 --- a/apps/sqlpage/latest/site-template/delete.sql +++ /dev/null @@ -1,16 +0,0 @@ -delete from todos -where id = $todo_id and $confirm = 'yes' -returning - 'redirect' as component, - '/' as link; - -select 'dynamic' as component, sqlpage.run_sql('shell.sql') as properties; - -select - 'alert' as component, - 'red' as color, - 'Confirm deletion' as title, - 'Are you sure you want to delete the following todo item?\n\n> ' || title as description_md, - '?todo_id=' || $todo_id || '&confirm=yes' as link, - 'Delete' as link_text -from todos where id = $todo_id; diff --git a/apps/sqlpage/latest/site-template/index.sql b/apps/sqlpage/latest/site-template/index.sql deleted file mode 100644 index 812957835..000000000 --- a/apps/sqlpage/latest/site-template/index.sql +++ /dev/null @@ -1,20 +0,0 @@ -select 'dynamic' as component, sqlpage.run_sql('shell.sql') as properties; - -select 'list' as component, - 'Todo' as title, - 'No todo yet...' as empty_title; - -select - title, - 'todo_form.sql?todo_id=' || id as edit_link, - 'delete.sql?todo_id=' || id as delete_link -from todos; - -select - 'button' as component, - 'center' as justify; -select - 'todo_form.sql' as link, - 'green' as color, - 'Add new todo' as title, - 'circle-plus' as icon; diff --git a/apps/sqlpage/latest/site-template/shell.sql b/apps/sqlpage/latest/site-template/shell.sql deleted file mode 100644 index 5531a6d25..000000000 --- a/apps/sqlpage/latest/site-template/shell.sql +++ /dev/null @@ -1,4 +0,0 @@ -select 'shell' as component, - printf('Todo list (%d)', count(*)) as title, - 'timeline' as menu_item -from todos; diff --git a/apps/sqlpage/latest/site-template/timeline.sql b/apps/sqlpage/latest/site-template/timeline.sql deleted file mode 100644 index f472c2489..000000000 --- a/apps/sqlpage/latest/site-template/timeline.sql +++ /dev/null @@ -1,12 +0,0 @@ -select 'dynamic' as component, sqlpage.run_sql('shell.sql') as properties; - -select 'timeline' as component; -select - title, - 'todo_form.sql?todo_id=' || id as link, - created_at as date, - 'calendar' as icon, - 'green' as color, - printf('%d days ago', julianday('now') - julianday(created_at)) as description -from todos -order by created_at desc; diff --git a/apps/sqlpage/latest/site-template/todo_form.sql b/apps/sqlpage/latest/site-template/todo_form.sql deleted file mode 100644 index 969069b60..000000000 --- a/apps/sqlpage/latest/site-template/todo_form.sql +++ /dev/null @@ -1,22 +0,0 @@ -insert or replace into todos(id, title) -select $todo_id, :todo -where :todo is not null -returning - 'redirect' as component, - '/' as link; - -select 'dynamic' as component, sqlpage.run_sql('shell.sql') as properties; - -select - 'form' as component, - 'Todo' as title, - case when $todo_id is null then - 'Add new todo' - else - 'Edit todo' - end as validate; -select - 'Todo item' as label, - 'todo' as name, - 'What do you have to do?' as placeholder, - (select title from todos where id = $todo_id) as value; diff --git a/apps/sqlpage/logo.png b/apps/sqlpage/logo.png index e20fcb01cdb66c0f2f7a816898458f1cbe5ed0ab..63ff06f2b8a4b20cd144a20a3d18960a4e5a87d7 100644 GIT binary patch delta 8841 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