From 80c080b8caed505cc784d93a6729d42901c1870a Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Sat, 29 Aug 2026 00:27:16 +0800 Subject: [PATCH] Update chatwoot app Rebuilt-from-PR: #6236 Source-PR: https://github.com/okxlin/appstore/pull/6236 Maintainer-workflow: appstore-pr-maintainer --- apps/chatwoot/data.yml | 34 +-- apps/chatwoot/latest/.env.sample | 2 +- apps/chatwoot/latest/data.yml | 282 ++++++++++++------------ apps/chatwoot/latest/docker-compose.yml | 4 +- apps/chatwoot/latest/scripts/init.sh | 107 ++++++++- apps/chatwoot/logo.png | Bin 8173 -> 2376 bytes 6 files changed, 265 insertions(+), 164 deletions(-) diff --git a/apps/chatwoot/data.yml b/apps/chatwoot/data.yml index 5791796f5..ccba9b248 100644 --- a/apps/chatwoot/data.yml +++ b/apps/chatwoot/data.yml @@ -1,30 +1,34 @@ name: Chatwoot tags: - - CRM +- CRM title: 开源全渠道客户支持平台 description: 开源全渠道客户支持平台 additionalProperties: key: chatwoot name: Chatwoot tags: - - CRM + - CRM + type: website + website: https://www.chatwoot.com/ + document: https://developers.chatwoot.com/self-hosted/deployment/docker + github: https://github.com/chatwoot/chatwoot shortDescZh: 开源全渠道客户支持平台 shortDescEn: Open source omnichannel customer support platform + crossVersionUpdate: false + limit: 0 + architectures: + - amd64 + - arm64 description: - en: An open source omnichannel customer support platform for live chat, email, inboxes and helpdesk workflows. + en: An open source omnichannel customer support platform for live chat, email, + inboxes and helpdesk workflows. zh: 面向在线客服、邮件、收件箱和工单流程的开源全渠道客户支持平台。 zh-Hant: 面向線上客服、郵件、收件匣和工單流程的開源全通路客戶支援平台。 ja: ライブチャット、メール、受信トレイ、ヘルプデスク業務向けのオープンソース全チャネル顧客サポート平台です。 ko: 라이브 채팅, 이메일, 받은 편지함 및 헬프데스크 워크플로를 위한 오픈소스 옴니채널 고객 지원 플랫폼입니다. - ru: Открытая омниканальная платформа поддержки клиентов для онлайн-чата, почты, входящих обращений и helpdesk-процессов. - ms: Platform sokongan pelanggan omnichannel sumber terbuka untuk sembang langsung, e-mel, peti masuk dan aliran kerja meja bantuan. - pt-br: Plataforma omnichannel open source de suporte ao cliente para chat ao vivo, email, caixas de entrada e fluxos de helpdesk. - type: website - crossVersionUpdate: false - limit: 0 - website: https://www.chatwoot.com/ - github: https://github.com/chatwoot/chatwoot - document: https://developers.chatwoot.com/self-hosted/deployment/docker - architectures: - - amd64 - - arm64 + ru: Открытая омниканальная платформа поддержки клиентов для онлайн-чата, почты, + входящих обращений и helpdesk-процессов. + ms: Platform sokongan pelanggan omnichannel sumber terbuka untuk sembang langsung, + e-mel, peti masuk dan aliran kerja meja bantuan. + pt-br: Plataforma omnichannel open source de suporte ao cliente para chat ao vivo, + email, caixas de entrada e fluxos de helpdesk. diff --git a/apps/chatwoot/latest/.env.sample b/apps/chatwoot/latest/.env.sample index e65203fd7..2989ba565 100644 --- a/apps/chatwoot/latest/.env.sample +++ b/apps/chatwoot/latest/.env.sample @@ -1,3 +1,4 @@ +CONTAINER_NAME=chatwoot-compose-check PANEL_APP_PORT_HTTP=18086 TZ=Asia/Shanghai APP_DATA_DIR=./data @@ -6,4 +7,3 @@ SECRET_KEY_BASE=chatwoot_secret_key_base POSTGRES_PASSWORD=chatwoot_postgres_password REDIS_PASSWORD=chatwoot_redis_password ENABLE_ACCOUNT_SIGNUP=true -CONTAINER_NAME=chatwoot diff --git a/apps/chatwoot/latest/data.yml b/apps/chatwoot/latest/data.yml index 81b5c4363..9aae7449d 100644 --- a/apps/chatwoot/latest/data.yml +++ b/apps/chatwoot/latest/data.yml @@ -1,143 +1,143 @@ additionalProperties: formFields: - - default: 18086 - 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: Asia/Shanghai - edit: true - envKey: TZ - labelEn: Timezone - labelZh: 时区 - label: - en: Timezone - zh: 时区 - zh-Hant: 時區 - ja: タイムゾーン - ko: 시간대 - ru: Часовой пояс - ms: Zon waktu - pt-br: Fuso horario - required: true - 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: Diretorio de dados - required: true - type: text - - default: http://1.2.3.4:18086 - edit: true - envKey: FRONTEND_URL - labelEn: Public URL - labelZh: 公开访问地址 - label: - en: Public URL - zh: 公开访问地址 - zh-Hant: 公開存取位址 - ja: 公開アクセス URL - ko: 공개 접속 주소 - ru: Публичный URL - ms: URL awam - pt-br: URL publico - required: true - rule: paramExtUrl - type: text - - default: chatwoot_secret_key_base - edit: true - envKey: SECRET_KEY_BASE - labelEn: Secret Key Base - labelZh: Rails 签名密钥 - label: - en: Secret Key Base - zh: Rails 签名密钥 - zh-Hant: Rails 簽章金鑰 - ja: Rails 署名キー - ko: Rails 서명 키 - ru: Ключ подписи Rails - ms: Kunci tandatangan Rails - pt-br: Chave de assinatura Rails - random: true - required: true - rule: paramComplexity - type: password - - default: chatwoot_postgres_password - edit: true - envKey: POSTGRES_PASSWORD - labelEn: PostgreSQL Password - labelZh: PostgreSQL 密码 - label: - en: PostgreSQL Password - zh: PostgreSQL 密码 - zh-Hant: PostgreSQL 密碼 - ja: PostgreSQL パスワード - ko: PostgreSQL 비밀번호 - ru: Пароль PostgreSQL - ms: Kata laluan PostgreSQL - pt-br: Senha do PostgreSQL - random: true - required: true - rule: paramComplexity - type: password - - default: chatwoot_redis_password - edit: true - envKey: REDIS_PASSWORD - labelEn: Redis Password - labelZh: Redis 密码 - label: - en: Redis Password - zh: Redis 密码 - zh-Hant: Redis 密碼 - ja: Redis パスワード - ko: Redis 비밀번호 - ru: Пароль Redis - ms: Kata laluan Redis - pt-br: Senha do Redis - random: true - required: true - rule: paramComplexity - type: password - - default: "true" - edit: true - envKey: ENABLE_ACCOUNT_SIGNUP - labelEn: Enable Account Signup - labelZh: 允许账号注册 - label: - en: Enable Account Signup - zh: 允许账号注册 - zh-Hant: 允許帳號註冊 - ja: アカウント登録を許可 - ko: 계정 가입 허용 - ru: Разрешить регистрацию аккаунтов - ms: Benarkan pendaftaran akaun - pt-br: Permitir cadastro de contas - required: true - type: select - values: - - label: "true" - value: "true" - - label: "false" - value: "false" + - default: 18086 + 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: Asia/Shanghai + edit: true + envKey: TZ + labelEn: Timezone + labelZh: 时区 + label: + en: Timezone + zh: 时区 + zh-Hant: 時區 + ja: タイムゾーン + ko: 시간대 + ru: Часовой пояс + ms: Zon waktu + pt-br: Fuso horario + required: true + 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: Diretorio de dados + required: true + type: text + - default: http://1.2.3.4:18086 + edit: true + envKey: FRONTEND_URL + labelEn: Public URL + labelZh: 公开访问地址 + label: + en: Public URL + zh: 公开访问地址 + zh-Hant: 公開存取位址 + ja: 公開アクセス URL + ko: 공개 접속 주소 + ru: Публичный URL + ms: URL awam + pt-br: URL publico + required: true + rule: paramExtUrl + type: text + - default: chatwoot_secret_key_base + edit: true + envKey: SECRET_KEY_BASE + labelEn: Secret Key Base + labelZh: Rails 签名密钥 + label: + en: Secret Key Base + zh: Rails 签名密钥 + zh-Hant: Rails 簽章金鑰 + ja: Rails 署名キー + ko: Rails 서명 키 + ru: Ключ подписи Rails + ms: Kunci tandatangan Rails + pt-br: Chave de assinatura Rails + random: true + required: true + rule: paramComplexity + type: password + - default: chatwoot_postgres_password + edit: true + envKey: POSTGRES_PASSWORD + labelEn: PostgreSQL Password + labelZh: PostgreSQL 密码 + label: + en: PostgreSQL Password + zh: PostgreSQL 密码 + zh-Hant: PostgreSQL 密碼 + ja: PostgreSQL パスワード + ko: PostgreSQL 비밀번호 + ru: Пароль PostgreSQL + ms: Kata laluan PostgreSQL + pt-br: Senha do PostgreSQL + random: true + required: true + rule: paramComplexity + type: password + - default: chatwoot_redis_password + edit: true + envKey: REDIS_PASSWORD + labelEn: Redis Password + labelZh: Redis 密码 + label: + en: Redis Password + zh: Redis 密码 + zh-Hant: Redis 密碼 + ja: Redis パスワード + ko: Redis 비밀번호 + ru: Пароль Redis + ms: Kata laluan Redis + pt-br: Senha do Redis + random: true + required: true + rule: paramComplexity + type: password + - default: 'true' + edit: true + envKey: ENABLE_ACCOUNT_SIGNUP + labelEn: Enable Account Signup + labelZh: 允许账号注册 + label: + en: Enable Account Signup + zh: 允许账号注册 + zh-Hant: 允許帳號註冊 + ja: アカウント登録を許可 + ko: 계정 가입 허용 + ru: Разрешить регистрацию аккаунтов + ms: Benarkan pendaftaran akaun + pt-br: Permitir cadastro de contas + required: true + type: select + values: + - label: 'true' + value: 'true' + - label: 'false' + value: 'false' diff --git a/apps/chatwoot/latest/docker-compose.yml b/apps/chatwoot/latest/docker-compose.yml index 347e59e9c..0f93b104c 100644 --- a/apps/chatwoot/latest/docker-compose.yml +++ b/apps/chatwoot/latest/docker-compose.yml @@ -1,6 +1,6 @@ services: chatwoot: - image: "chatwoot/chatwoot:v4.17.0-ce" + image: "chatwoot/chatwoot:v4.17.1-ce" container_name: ${CONTAINER_NAME} restart: always depends_on: @@ -49,7 +49,7 @@ services: createdBy: "Apps" chatwoot-sidekiq: - image: "chatwoot/chatwoot:v4.17.0-ce" + image: "chatwoot/chatwoot:v4.17.1-ce" container_name: ${CONTAINER_NAME}-sidekiq restart: always depends_on: diff --git a/apps/chatwoot/latest/scripts/init.sh b/apps/chatwoot/latest/scripts/init.sh index 6062c6d34..253e8bd06 100755 --- a/apps/chatwoot/latest/scripts/init.sh +++ b/apps/chatwoot/latest/scripts/init.sh @@ -1,8 +1,105 @@ #!/usr/bin/env bash set -euo pipefail +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +export PATH -DATA_DIR="${APP_DATA_DIR:-./data}" -mkdir -p \ - "${DATA_DIR}/storage" \ - "${DATA_DIR}/postgres" \ - "${DATA_DIR}/redis" +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}" + +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" +} + +resolve_direct_child() { + local key="$1" + local raw="$2" + local clean path + clean="${raw#./}" + if [[ -z "$clean" || "$clean" == */* ]]; then + echo "unsafe ${key} path: lifecycle directories must be direct children of the version root" >&2 + return 1 + fi + path="$(resolve_app_path "$key" "$raw")" + [[ "$path" == "$ROOT_DIR/$clean" ]] || { echo "unsafe ${key} path" >&2; return 1; } + printf '%s\n' "$path" +} + +verify_trusted_root_chain() { + local current owner mode + [[ "$(id -u)" == "0" ]] || { echo "directory ownership initialization must run as root" >&2; return 1; } + command -v stat >/dev/null 2>&1 || { echo "stat is required" >&2; return 1; } + current="$ROOT_DIR" + while [[ "$current" != "/" ]]; do + [[ -d "$current" && ! -L "$current" ]] || { echo "unsafe version root chain: $current" >&2; return 1; } + IFS=':' read -r owner mode < <(stat -c '%u:%a' -- "$current") + [[ "$owner" == "0" ]] || { echo "unsafe version root chain owner: $current" >&2; return 1; } + [[ "$mode" =~ ^[0-7]{3,4}$ ]] || { echo "unsafe version root chain mode: $current" >&2; return 1; } + (( (8#$mode & 0022) == 0 )) || { echo "unsafe version root chain permissions: $current" >&2; return 1; } + current="$(dirname -- "$current")" + done +} + +ensure_dir() { + local key="$1" + local raw + local path + raw="$(configured_value "$key" "$2")" + path="$(resolve_app_path "$key" "$raw")" + mkdir -p -- "$path" + [[ "$(resolve_app_path "$key" "$raw")" == "$path" ]] || { echo "unsafe ${key} path" >&2; return 1; } +} + +ensure_dir "APP_DATA_DIR" "./data" diff --git a/apps/chatwoot/logo.png b/apps/chatwoot/logo.png index d72c123988bd089e079f7abe79b16234fef1b91f..4fc8a64888363ce8a47a2592afc6223c97f8f7d2 100644 GIT binary patch literal 2376 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 UrKjaoU^D=<)eT@xP=}cR10+h+;{X5v