diff --git a/apps/leafwiki/0.12.0/.env.sample b/apps/leafwiki/0.12.0/.env.sample new file mode 100644 index 000000000..5535efdf0 --- /dev/null +++ b/apps/leafwiki/0.12.0/.env.sample @@ -0,0 +1,7 @@ +PANEL_APP_PORT_HTTP=8080 +LEAFWIKI_ADMIN_USERNAME=admin +LEAFWIKI_ADMIN_EMAIL=admin@localhost +LEAFWIKI_ADMIN_PASSWORD=LeafWiki_Admin +LEAFWIKI_ALLOW_INSECURE=false +APP_DATA_DIR=./data +CONTAINER_NAME= diff --git a/apps/leafwiki/0.12.0/data.yml b/apps/leafwiki/0.12.0/data.yml new file mode 100644 index 000000000..3d927fa71 --- /dev/null +++ b/apps/leafwiki/0.12.0/data.yml @@ -0,0 +1,107 @@ +additionalProperties: + formFields: + - default: 8080 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Port + labelZh: 端口 + label: + en: Port + zh: 端口 + zh-Hant: 埠 + ja: ポート + ko: 포트 + ru: Порт + ms: Port + pt-br: Porta + required: true + rule: paramPort + type: number + - default: admin + edit: true + envKey: LEAFWIKI_ADMIN_USERNAME + labelEn: Initial Admin Username + labelZh: 初始管理员用户名 + label: + en: Initial Admin Username + zh: 初始管理员用户名 + zh-Hant: 初始管理員使用者名稱 + ja: 初期管理者ユーザー名 + ko: 초기 관리자 사용자 이름 + ru: Имя начального администратора + ms: Nama Pengguna Pentadbir Awal + pt-br: Nome do Administrador Inicial + required: true + rule: paramCommon + type: text + - default: admin@localhost + edit: true + envKey: LEAFWIKI_ADMIN_EMAIL + labelEn: Initial Admin Email + labelZh: 初始管理员邮箱 + label: + en: Initial Admin Email + zh: 初始管理员邮箱 + zh-Hant: 初始管理員電子郵件 + ja: 初期管理者メール + ko: 초기 관리자 이메일 + ru: Email начального администратора + ms: E-mel Pentadbir Awal + pt-br: E-mail do Administrador Inicial + required: true + type: text + - default: LeafWiki_Admin + edit: true + envKey: LEAFWIKI_ADMIN_PASSWORD + labelEn: Initial Admin Password + labelZh: 初始管理员密码 + label: + en: Initial Admin Password + zh: 初始管理员密码 + zh-Hant: 初始管理員密碼 + ja: 初期管理者パスワード + ko: 초기 관리자 비밀번호 + ru: Пароль начального администратора + ms: Kata Laluan Pentadbir Awal + pt-br: Senha do Administrador Inicial + random: true + required: true + rule: paramComplexity + type: password + - default: "false" + edit: true + envKey: LEAFWIKI_ALLOW_INSECURE + labelEn: Allow Plain HTTP Login (Insecure, Trusted LAN Only) + labelZh: 允许明文 HTTP 登录(不安全,仅限可信局域网) + label: + en: Allow Plain HTTP Login (Insecure, Trusted LAN Only) + zh: 允许明文 HTTP 登录(不安全,仅限可信局域网) + zh-Hant: 允許明文 HTTP 登入(不安全,僅限可信區域網路) + ja: 平文 HTTP ログインを許可(非推奨) + ko: 일반 HTTP 로그인 허용(안전하지 않음) + ru: Разрешить вход по HTTP без шифрования (небезопасно) + ms: Benarkan Log Masuk HTTP Biasa (Tidak Selamat) + pt-br: Permitir Login HTTP sem Criptografia (Inseguro) + required: true + type: select + values: + - label: "true" + value: "true" + - label: "false" + value: "false" + - 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/leafwiki/0.12.0/data/.gitkeep b/apps/leafwiki/0.12.0/data/.gitkeep new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/leafwiki/0.12.0/data/.gitkeep @@ -0,0 +1 @@ + diff --git a/apps/leafwiki/0.12.0/docker-compose.yml b/apps/leafwiki/0.12.0/docker-compose.yml new file mode 100644 index 000000000..01c9a11eb --- /dev/null +++ b/apps/leafwiki/0.12.0/docker-compose.yml @@ -0,0 +1,43 @@ +services: + leafwiki: + image: "ghcr.io/perber/leafwiki:v0.12.0@sha256:a4629aba418ddf6e70f8c9e1f723aff32b3715217cc1b8c3959a43ce2cf8fc2b" + container_name: ${CONTAINER_NAME} + restart: unless-stopped + init: true + user: "1000:1000" + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:8080" + env_file: + - path: "${APP_DATA_DIR}/.leafwiki-secrets.env" + required: false + environment: + - LEAFWIKI_DATA_DIR=/app/data + - LEAFWIKI_ADMIN_PASSWORD=${LEAFWIKI_ADMIN_PASSWORD} + - LEAFWIKI_ADMIN_USERNAME=${LEAFWIKI_ADMIN_USERNAME} + - LEAFWIKI_ADMIN_EMAIL=${LEAFWIKI_ADMIN_EMAIL} + - LEAFWIKI_ALLOW_INSECURE=${LEAFWIKI_ALLOW_INSECURE} + - LEAFWIKI_DISABLE_AUTH=false + - LEAFWIKI_PUBLIC_ACCESS=false + read_only: true + tmpfs: + - /tmp:size=32m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + healthcheck: + test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/api/health"] + interval: 30s + timeout: 5s + start_period: 10s + retries: 3 + volumes: + - "${APP_DATA_DIR}:/app/data" + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/leafwiki/0.12.0/scripts/init.sh b/apps/leafwiki/0.12.0/scripts/init.sh new file mode 100755 index 000000000..4f1aa1352 --- /dev/null +++ b/apps/leafwiki/0.12.0/scripts/init.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +set -euo pipefail + +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="${!key:-}" + if [[ -z "$value" ]]; then + value="$(read_env_value "$key")" + fi + printf '%s\n' "${value:-$default_value}" +} + +prepare_data_dir() { + local raw path secrets_file temp_file jwt_secret totp_key + raw="$(configured_value APP_DATA_DIR ./data)" + + [[ -n "$raw" ]] || { + printf '%s\n' 'APP_DATA_DIR must not be empty' >&2 + exit 1 + } + if [[ "$raw" = /* ]]; then + printf '%s\n' 'APP_DATA_DIR must be relative to the application version directory' >&2 + exit 1 + fi + + path="$(realpath -m -- "$ROOT_DIR/${raw#./}")" + case "$path" in + "$ROOT_DIR"/*) ;; + *) + printf '%s\n' 'APP_DATA_DIR must remain inside the application version directory' >&2 + exit 1 + ;; + esac + + install -d -m 0750 "$path" + path="$(realpath -e -- "$path")" + case "$path" in + "$ROOT_DIR"/*) ;; + *) + printf '%s\n' 'APP_DATA_DIR resolves outside the application version directory' >&2 + exit 1 + ;; + esac + chmod 0750 "$path" + chown -R --no-dereference 1000:1000 "$path" + + secrets_file="$path/.leafwiki-secrets.env" + if [[ -L "$secrets_file" ]]; then + printf '%s\n' 'LeafWiki secrets file must not be a symbolic link' >&2 + exit 1 + fi + if [[ ! -e "$secrets_file" ]]; then + umask 077 + jwt_secret="$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n')" + totp_key="$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n')" + [[ ${#jwt_secret} -eq 64 && ${#totp_key} -eq 64 ]] || { + printf '%s\n' 'Failed to generate LeafWiki secrets' >&2 + exit 1 + } + temp_file="$(mktemp "$path/.leafwiki-secrets.env.tmp.XXXXXX")" + trap 'rm -f -- "${temp_file:-}"' EXIT + printf 'LEAFWIKI_JWT_SECRET=%s\nLEAFWIKI_TOTP_ENCRYPTION_KEY=%s\n' \ + "$jwt_secret" "$totp_key" >"$temp_file" + chmod 0600 "$temp_file" + chown 1000:1000 "$temp_file" + mv -f -- "$temp_file" "$secrets_file" + trap - EXIT + fi + [[ -f "$secrets_file" ]] || { + printf '%s\n' 'LeafWiki secrets path must be a regular file' >&2 + exit 1 + } + [[ "$(grep -c '^LEAFWIKI_JWT_SECRET=' "$secrets_file")" -eq 1 ]] || { + printf '%s\n' 'LeafWiki secrets file must contain exactly one JWT secret' >&2 + exit 1 + } + [[ "$(grep -c '^LEAFWIKI_TOTP_ENCRYPTION_KEY=' "$secrets_file")" -eq 1 ]] || { + printf '%s\n' 'LeafWiki secrets file must contain exactly one TOTP encryption key' >&2 + exit 1 + } + jwt_secret="$(sed -n 's/^LEAFWIKI_JWT_SECRET=//p' "$secrets_file")" + totp_key="$(sed -n 's/^LEAFWIKI_TOTP_ENCRYPTION_KEY=//p' "$secrets_file")" + [[ "$jwt_secret" =~ ^[0-9a-f]{64}$ ]] || { + printf '%s\n' 'LeafWiki JWT secret must be a 256-bit hexadecimal value' >&2 + exit 1 + } + [[ "$totp_key" =~ ^[0-9a-f]{64}$ ]] || { + printf '%s\n' 'LeafWiki TOTP encryption key must be a 256-bit hexadecimal value' >&2 + exit 1 + } + [[ "$(stat -c '%a' -- "$secrets_file")" = 600 ]] || { + printf '%s\n' 'LeafWiki secrets file permissions must be 0600' >&2 + exit 1 + } +} + +prepare_data_dir diff --git a/apps/leafwiki/0.12.0/scripts/uninstall.sh b/apps/leafwiki/0.12.0/scripts/uninstall.sh new file mode 100755 index 000000000..ab8a8d674 --- /dev/null +++ b/apps/leafwiki/0.12.0/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker-compose down --volumes diff --git a/apps/leafwiki/0.12.0/scripts/upgrade.sh b/apps/leafwiki/0.12.0/scripts/upgrade.sh new file mode 100755 index 000000000..692e341e2 --- /dev/null +++ b/apps/leafwiki/0.12.0/scripts/upgrade.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +"$(dirname "$0")/init.sh" diff --git a/apps/leafwiki/README.md b/apps/leafwiki/README.md new file mode 100644 index 000000000..ef89d4e4e --- /dev/null +++ b/apps/leafwiki/README.md @@ -0,0 +1,62 @@ +# LeafWiki + +## 产品介绍 + +LeafWiki 是一个轻量级自托管 Wiki,以目录和文件夹结构组织 Markdown 页面,提供全文搜索、标签、页面历史、备份和多用户权限管理。 + +## 主要功能 + +- 以目录树组织 Markdown 页面 +- 全文搜索、标签和页面链接 +- 管理员、编辑者和只读用户角色 +- TOTP 双因素认证、快照和 Git 备份 + +## 访问说明 + +安装后先在 1Panel 中为应用配置可信 HTTPS 反向代理,再通过 `https://<配置的域名>` 访问,并使用安装时设置的初始管理员用户名和密码登录。初始管理员参数只在首次创建用户数据库时生效;之后请在 LeafWiki 中管理账号和密码。 + +默认关闭“允许明文 HTTP 登录(不安全)”,保持上游安全默认。应用的明文端口可用于健康检查,但登录必须通过可信 HTTPS 反向代理,并正确传递 `X-Forwarded-Proto: https`。只有在端口严格限制于可信局域网且无法配置 HTTPS 时,才可显式把该选项改为 `true`;此时认证 Cookie 会通过未加密网络传输,绝不能把服务暴露到公网。 + +## 数据持久化 + +`APP_DATA_DIR` 挂载到 `/app/data`,保存 Markdown 页面、SQLite 用户数据库、资源、索引、快照和配置。该路径必须位于应用版本目录内,默认值为 `./data`;初始化脚本会拒绝绝对路径和目录外路径,并将其设置给官方非 root 用户 UID/GID `1000:1000`。脚本还会在该目录中首次生成并持久保存两枚 256-bit JWT/TOTP 密钥,不会在升级或重启时重新生成。卸载不会删除绑定目录中的用户数据,升级或迁移前请单独备份。 + +## 安全与部署风险 + +- 认证和刷新令牌限流保持启用;包内没有启用公开读取、无认证模式、API 密钥管理或 Git 备份。容器以 UID/GID `1000:1000` 运行,根文件系统只读,丢弃全部 Linux capabilities,并启用 `no-new-privileges`。 +- 对固定镜像执行的 2026-07-28 Trivy 扫描发现 `0` 个 Critical 和 `1` 个 High:`CVE-2026-39822`(Go `os.Root` 符号链接目录逃逸)。镜像使用 Go 1.26.4,修复版本为 1.26.5;源码级 Go 漏洞扫描未找到 LeafWiki 调用受影响 `os.Root` API 的路径,但仍应在上游发布修复镜像后尽快更新。 +- 同一次源码级 Go 漏洞扫描发现 `GO-2026-5856`(`CVE-2026-42505`,ECH 客户端握手隐私泄露)存在通用 TLS 调用链。该问题只在 LeafWiki 作为客户端使用 Encrypted Client Hello 时生效;默认关闭的 Git 备份和普通入站 HTTP 服务不启用 ECH。扫描还在依赖元数据或二进制符号中报告 `GO-2026-5970`、`GO-2026-5942` 和 `GO-2026-5932`,但未发现 LeafWiki 调用其受影响符号。 + +## Introduction + +LeafWiki is a lightweight self-hosted wiki that organizes Markdown pages in a folder tree and provides full-text search, tags, page history, backups, and multi-user access control. + +## Features + +- Folder-oriented Markdown page tree +- Full-text search, tags, and page links +- Administrator, editor, and viewer roles +- TOTP two-factor authentication, snapshots, and Git backups + +## Usage Notes + +Configure a trusted HTTPS reverse proxy for the app in 1Panel, then access it at `https://` and sign in with the initial administrator credentials selected during installation. Initial administrator settings apply only when the user database is first created; manage later account changes inside LeafWiki. + +The package keeps **Allow Plain HTTP Login (Insecure)** disabled by default, matching the upstream secure default. The plain application port remains available for health checks, but login requires a trusted HTTPS reverse proxy that forwards `X-Forwarded-Proto: https`. Enable the insecure option explicitly only when the port is strictly limited to a trusted LAN and HTTPS cannot be configured; authentication cookies then cross the network unencrypted, so never expose that mode to the public Internet. + +`APP_DATA_DIR` is mounted at `/app/data` and stores Markdown pages, the SQLite user database, assets, indexes, snapshots, and configuration. It must remain relative to the application version directory and is prepared for UID/GID `1000:1000`. On first install, the initialization script also generates and persists separate 256-bit JWT and TOTP keys in this directory; upgrades and restarts do not regenerate them. Uninstall does not delete bind-mounted user data; back it up before upgrades or migration. + +## Security and Deployment Risks + +- Authentication and refresh-token rate limiting remain enabled. Public access, authentication bypass, API-key management, and Git backup are not enabled by this package. The container runs as UID/GID `1000:1000`, uses a read-only root filesystem, drops all Linux capabilities, and enables `no-new-privileges`. +- A 2026-07-28 Trivy scan of the pinned image found 0 Critical and 1 High finding: `CVE-2026-39822`, a symlink root escape in Go `os.Root`. The image was built with Go 1.26.4 and the fix is in 1.26.5. Source-mode Go vulnerability analysis found no LeafWiki call path to the affected `os.Root` APIs, but update promptly when upstream publishes a fixed image. +- The same source-mode Go scan found a generic TLS call path for `GO-2026-5856` (`CVE-2026-42505`), an ECH client-handshake privacy leak. It applies only when LeafWiki acts as a client using Encrypted Client Hello; the default-disabled Git backup and ordinary inbound HTTP service do not enable ECH. The scan also reported `GO-2026-5970`, `GO-2026-5942`, and `GO-2026-5932` in dependency metadata or binary symbols without finding calls from LeafWiki to their affected symbols. + +## References + +- Project: +- Release: +- Container source: +- Configuration: +- License: (MIT) +- Logo source: (ISC) diff --git a/apps/leafwiki/data.yml b/apps/leafwiki/data.yml new file mode 100644 index 000000000..d1e6a3df0 --- /dev/null +++ b/apps/leafwiki/data.yml @@ -0,0 +1,30 @@ +name: LeafWiki +tags: + - Tool +title: 基于目录组织的轻量级 Wiki +description: 基于目录组织的轻量级 Wiki +additionalProperties: + key: leafwiki + name: LeafWiki + tags: + - Tool + shortDescZh: 基于目录组织的轻量级 Wiki + shortDescEn: A lightweight, folder-oriented wiki + description: + en: A lightweight, folder-oriented wiki + zh: 基于目录组织的轻量级 Wiki + zh-Hant: 以目錄組織的輕量級 Wiki + ja: フォルダー指向の軽量 Wiki + ko: 폴더 중심의 경량 Wiki + ru: Легкая Wiki с организацией по папкам + ms: Wiki ringan berasaskan folder + pt-br: Wiki leve organizada por pastas + type: website + crossVersionUpdate: true + limit: 0 + website: https://leafwiki.com/ + github: https://github.com/perber/leafwiki + document: https://github.com/perber/leafwiki/blob/main/README.md + architectures: + - amd64 + - arm64 diff --git a/apps/leafwiki/logo-LICENSE.txt b/apps/leafwiki/logo-LICENSE.txt new file mode 100644 index 000000000..0ab936cd8 --- /dev/null +++ b/apps/leafwiki/logo-LICENSE.txt @@ -0,0 +1,23 @@ +LeafWiki app-store logo asset + +Source: https://github.com/lucide-icons/lucide/blob/0.468.0/icons/leaf.svg +Source version: Lucide 0.468.0 +Modification: rendered as a 180x180 green PNG with a transparent background. + +ISC License + +Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part +of Feather (MIT). All other copyright (c) for Lucide are held by Lucide +Contributors 2022. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/apps/leafwiki/logo.png b/apps/leafwiki/logo.png new file mode 100644 index 000000000..3f2069ada Binary files /dev/null and b/apps/leafwiki/logo.png differ