From 81a50313c3326de6d58f8b3b8c679354a6a2c5a0 Mon Sep 17 00:00:00 2001 From: Gloridust Date: Fri, 29 May 2026 15:26:59 +0800 Subject: [PATCH] init --- .env.example | 16 ++++ .gitignore | 3 + README.md | 120 ++++++++++++++++++++++++++++ docker-compose.yml | 35 +++++++++ docker/Dockerfile | 78 +++++++++++++++++++ docker/autostart | 27 +++++++ 技术方案.md | 190 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 469 insertions(+) create mode 100644 .env.example create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 docker/Dockerfile create mode 100644 docker/autostart create mode 100644 技术方案.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2e207b6 --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +# 复制本文件为 .env 即可覆盖默认配置。全部可选——不建 .env 也能直接 `docker compose up -d --build`。 + +# web 端登录账号 / 密码(KasmVNC 基础鉴权)。强烈建议改掉默认密码! +WOC_USER=admin +WOC_PASSWORD=wechat + +# 宿主用户 uid/gid(飞牛上用 `id` 命令查看;单用户 NAS 一般是 1000) +WOC_PUID=1000 +WOC_PGID=1000 + +# 时区 +WOC_TZ=Asia/Shanghai + +# 对外端口(宿主侧,默认用冷门端口避免冲突;容器内仍是 3000/3001) +WOC_HTTP_PORT=36080 +WOC_HTTPS_PORT=36443 diff --git a/.gitignore b/.gitignore index 1170717..1eac64b 100644 --- a/.gitignore +++ b/.gitignore @@ -134,3 +134,6 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +# WechatOnCloud: 微信运行数据(登录态/消息,大且敏感,勿提交) +/data/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..22b97c1 --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# WechatOnCloud + +在飞牛 NAS(x86_64)上运行服务端微信,多个 web 用户通过浏览器访问**同一个**微信会话,实现跨设备消息同步、多端共享。 + +> 设计与选型详见 [技术方案.md](技术方案.md)。 +> 本仓库当前是 **MVP**:Docker + 官方微信 Linux 原生版 + KasmVNC 串流到浏览器。 + +--- + +## 工作原理(一句话) + +容器里跑 Xvfb 虚拟显示 + 官方原版微信,KasmVNC 把画面串到浏览器。多个浏览器连同一容器 = 共享同一个微信会话。**不修改微信客户端**。 + +--- + +## 快速开始(零配置) + +> 需已安装 Docker(含 Compose 插件)。x86_64 / arm64 均可。 + +```bash +git clone WechatOnCloud +cd WechatOnCloud +docker compose up -d --build +``` + +就这一条。然后浏览器访问 `http://:36080`(或 `https://:36443` 自签证书), +用默认账号 **admin / wechat** 登录 web 端,再用手机扫码登录微信即可。 + +> 宿主端口默认用冷门的 `36080/36443` 避免与其他服务冲突;容器内仍是 3000/3001。要改见 `.env`。 + +> 首次构建会下载约 190~210MB 的微信安装包,较慢,属正常。 + +### 架构自动适配 + +构建时会**自动检测本机 CPU 架构**(Docker BuildKit 的 `TARGETARCH`),下载对应的官方微信包: + +| 构建机器 | 架构 | 自动下载 | +|----------|------|----------| +| Intel/AMD NAS、x86 服务器 | amd64 | `WeChatLinux_x86_64.deb` | +| ARM NAS、Apple Silicon Mac | arm64 | `WeChatLinux_arm64.deb` | + +所以**在你的 Mac(M 系列)上直接 `docker compose up -d --build` 就能本地调试**,到飞牛上(无论 x64 还是 arm)同样一条命令,无需改任何架构相关配置。 + +> 仅当你要在 A 架构机器上为 B 架构 NAS **交叉构建**时,才需在 `docker-compose.yml` 里取消 `platforms` 注释并指定目标架构。 + +### 自定义配置(可选) + +默认值开箱即用,无需任何配置。要改的话,复制一份 `.env`: + +```bash +cp .env.example .env # 然后按需修改,最该改的是 WOC_PASSWORD +``` + +可配置项见 [.env.example](.env.example):web 账号密码、PUID/PGID、时区、端口。 + +--- + +## 数据持久化 + +微信登录态与消息都写在容器内 `HOME=/config`,已映射到宿主 `./data`。 +- **不要删 `./data`**,否则要重新扫码登录、丢本地消息缓存。 +- 备份微信 = 备份 `./data` 目录。 + +--- + +## ⚠️ 安全须知(必读) + +这套系统暴露的是一个**已登录的微信**——能访问端口的人就能看你聊天记录、以你身份发消息。 + +MVP 仅依赖 KasmVNC 自带的 web 端基础鉴权(`CUSTOM_USER`/`PASSWORD`)。**生产使用务必:** + +- 只在内网访问,或经飞牛远程访问 / VPN / 内网穿透,**不要直接裸暴露公网**; +- 务必改掉默认密码(默认 admin / wechat):`cp .env.example .env` 后改 `WOC_PASSWORD`; +- 进一步加固(独立鉴权层 Authelia、反代 TLS、陌生设备验证码)见 [技术方案.md](技术方案.md) 第 5 节,属后续迭代。 + +--- + +## 常见问题 + +| 现象 | 排查 | +|------|------| +| 界面/消息显示成方块 | 中文字体没装好,确认镜像构建时 `fonts-noto-cjk` 安装成功 | +| 微信起不来 / 黑屏 | 看日志 `docker logs wechat-on-cloud`;确认 `security_opt: seccomp:unconfined` 与 `shm_size` 已生效。微信 deb 漏声明的运行时依赖(libatomic1、xcb 系、GTK3、Chromium 所需 X 扩展等)已在 Dockerfile 内置 | +| 排查缺哪个库 | `docker exec wechat-on-cloud ldd /opt/wechat/wechat` 及 `.../RadiumWMPF/runtime/WeChatAppEx`,看 `not found` 的项,补进 Dockerfile 的依赖层 | +| 多人同时操作很乱 | 已知问题:单会话多端共享、键鼠会打架。MVP 未做并发控制,建议同一时刻一人操作(见技术方案 6.1) | +| 过段时间掉登录 | 微信桌面会话会定期失效,需手机重新扫码(见技术方案 6.2) | +| 下载 deb 失败 | 腾讯 CDN 偶发波动,重试 `docker compose build --no-cache`;Dockerfile 已内置主/备 CDN 自动回退 | +| 架构不支持报错 | 微信仅提供 x86_64 / arm64;若构建机是其他架构会直接报错退出 | + +查看运行日志: + +```bash +docker logs -f wechat-on-cloud +``` + +--- + +## 目录结构 + +``` +WechatOnCloud/ +├── docker/ +│ ├── Dockerfile # KasmVNC base + 中文字体 + 按架构自动下载微信原生版 deb +│ └── autostart # openbox 会话启动时拉起微信(含崩溃自重启) +├── docker-compose.yml # 端口 / 数据卷 / seccomp / 鉴权(缺省值开箱即用) +├── .env.example # 可选配置项(账号密码、PUID/PGID、端口、时区) +├── 技术方案.md # 完整设计文档 +└── README.md +``` + +--- + +## 路线图 + +- [x] MVP:Docker + 微信原生版 + KasmVNC,浏览器扫码登录、收发消息 +- [ ] 反代 + 独立鉴权 + TLS 加固 +- [ ] 多端并发控制(控制权令牌) +- [ ] 掉登录时 web 端二维码重扫入口 +- [ ] 换 Xpra 做"单窗口独立 App"观感 +- [ ] 打包成飞牛原生 fpk 分发 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2bdaa76 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,35 @@ +services: + wechat: + build: + context: ./docker + dockerfile: Dockerfile + # 不指定 platform:BuildKit 默认按本机架构构建并自动下载对应微信包。 + # 仅当需要在 A 架构机器上为 B 架构 NAS 交叉构建时,才取消下一行注释并改成目标架构。 + # platforms: ["linux/arm64"] + image: wechat-on-cloud:latest + container_name: wechat-on-cloud + + # 以下全部可不填:缺省值开箱即用。要改只需复制 .env.example 为 .env 后修改。 + environment: + - PUID=${WOC_PUID:-1000} + - PGID=${WOC_PGID:-1000} + - TZ=${WOC_TZ:-Asia/Shanghai} + # KasmVNC 内置 web 端基础鉴权(MVP 阶段最低限度访问控制) + - CUSTOM_USER=${WOC_USER:-admin} + - PASSWORD=${WOC_PASSWORD:-wechat} + + volumes: + # 持久化登录态与消息:微信数据写在容器内 HOME=/config 下 + - ./data:/config + + ports: + - "${WOC_HTTP_PORT:-36080}:3000" # HTTP web 客户端(宿主用冷门端口避免冲突) + - "${WOC_HTTPS_PORT:-36443}:3001" # HTTPS web 客户端(自签证书) + + # 微信内部沙箱需要,否则可能起不来 + security_opt: + - seccomp:unconfined + # 微信/Chromium 系共享内存,过小会崩 + shm_size: "1gb" + + restart: unless-stopped diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..1dde404 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,78 @@ +# WechatOnCloud —— 在浏览器里访问服务端微信 +# 基于 linuxserver 的 KasmVNC 基础镜像(多架构 amd64/arm64): +# 自带 Xvfb + openbox + KasmVNC + web 客户端(3000/3001) +FROM lscr.io/linuxserver/baseimage-kasmvnc:debianbookworm + +# TARGETARCH 由 Docker BuildKit 自动注入:本机构建=本机架构, +# 因此在 x86 NAS / arm NAS / Apple Silicon Mac 上 `docker compose build` 都会自动选对包。 +ARG TARGETARCH +# CDN 主链失败时回退备链 +ARG WECHAT_CDN="https://dldir1v6.qq.com/weixin/Universal/Linux" +ARG WECHAT_CDN_FALLBACK="https://dldir1.qq.com/weixin/Universal/Linux" + +ENV DEBIAN_FRONTEND=noninteractive + +RUN set -eux; \ + # 1) 按架构映射微信 deb 文件名 + case "${TARGETARCH}" in \ + amd64) WECHAT_FILE="WeChatLinux_x86_64.deb" ;; \ + arm64) WECHAT_FILE="WeChatLinux_arm64.deb" ;; \ + *) echo "不支持的架构: ${TARGETARCH}(微信仅提供 x86_64 / arm64)" >&2; exit 1 ;; \ + esac; \ + echo "构建架构=${TARGETARCH}, 微信包=${WECHAT_FILE}"; \ + # 2) 依赖:中文字体(否则界面/消息显示方块)+ 语言环境 + 下载工具 + apt-get update; \ + apt-get install -y --no-install-recommends \ + curl ca-certificates locales \ + fonts-wqy-zenhei fonts-wqy-microhei fonts-noto-cjk fonts-noto-color-emoji \ + libnss3 libgbm1 libasound2 libxss1; \ + sed -i 's/# zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen; \ + locale-gen; \ + # 3) 下载微信 deb(主链失败回退备链) + ( curl -fSL --retry 3 -A "Mozilla/5.0" -o /tmp/wechat.deb "${WECHAT_CDN}/${WECHAT_FILE}" \ + || curl -fSL --retry 3 -A "Mozilla/5.0" -o /tmp/wechat.deb "${WECHAT_CDN_FALLBACK}/${WECHAT_FILE}" ); \ + # 4) 用 apt 安装本地 deb 以自动解决依赖 + apt-get install -y --no-install-recommends /tmp/wechat.deb; \ + rm -f /tmp/wechat.deb; \ + apt-get clean; \ + rm -rf /var/lib/apt/lists/* + +# 微信 deb 未声明、但运行时需要的额外库(单独成层,避免动到上面缓存的下载层)。 +# 微信原生版是 Qt 程序,依赖一组 xcb 平台库;libxcb-cursor0 由 Qt 动态 dlopen,ldd 查不到,需主动装。 +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + libatomic1 \ + libxdamage1 \ + libxkbcommon-x11-0 \ + libxcb-icccm4 \ + libxcb-image0 \ + libxcb-keysyms1 \ + libxcb-render-util0 \ + libxcb-xkb1 \ + libxcb-cursor0 \ + # WeChatAppEx 是 Chromium 内核,需 GTK3 全家桶 + 一组 X 扩展 + cups + libgtk-3-0 \ + libatk1.0-0 \ + libatk-bridge2.0-0 \ + libatspi2.0-0 \ + libcups2 \ + libxcomposite1 \ + libxrandr2 \ + libxfixes3 \ + libxtst6 \ + libxshmfence1 \ + libdrm2; \ + apt-get clean; \ + rm -rf /var/lib/apt/lists/* + +ENV LANG=zh_CN.UTF-8 \ + LC_ALL=zh_CN.UTF-8 \ + LIBGL_ALWAYS_SOFTWARE=1 + +# openbox 会话启动时执行此脚本拉起微信 +COPY autostart /defaults/autostart +RUN chmod +x /defaults/autostart + +# 3000 = HTTP web 客户端, 3001 = HTTPS +EXPOSE 3000 3001 diff --git a/docker/autostart b/docker/autostart new file mode 100644 index 0000000..a2d3628 --- /dev/null +++ b/docker/autostart @@ -0,0 +1,27 @@ +#!/bin/bash +# 由 KasmVNC base 的 openbox 会话在桌面就绪后执行。 +# 定位微信可执行文件并常驻拉起:用户在 web 端关掉窗口后会自动重开,避免黑屏。 + +set -u + +# 官方 deb 安装到 /opt/wechat/wechat;同时兜底查 PATH +if [ -x /opt/wechat/wechat ]; then + WECHAT_BIN=/opt/wechat/wechat +else + WECHAT_BIN="$(command -v wechat || true)" +fi + +if [ -z "${WECHAT_BIN}" ]; then + echo "[autostart] 未找到微信可执行文件,请检查 deb 是否安装成功" >&2 + exit 1 +fi + +# 容器内无 GPU,强制软件渲染 +export LIBGL_ALWAYS_SOFTWARE=1 + +while true; do + echo "[autostart] 启动微信: ${WECHAT_BIN}" + "${WECHAT_BIN}" + echo "[autostart] 微信已退出,2 秒后重启" + sleep 2 +done diff --git a/技术方案.md b/技术方案.md new file mode 100644 index 0000000..7505c28 --- /dev/null +++ b/技术方案.md @@ -0,0 +1,190 @@ +# WechatOnCloud 技术方案 + +> 目标:在飞牛 NAS(x86_64)上运行一个服务端微信,多个 web 用户通过浏览器访问同一个微信会话,实现跨设备消息同步、多端共享,解决原生微信"一台电脑一个登录"的痛点。 +> 参考形态:懒猫微服「云微信」。 + +--- + +## 1. 核心认知:单会话多端共享,而非多用户登录 + +微信桌面端的登录是**手机扫码绑定的单一会话**——同一账号同一时刻只能有一个桌面端在线。 + +因此"多个 web 用户登录同一个微信"的本质是: + +> **服务器上跑 1 个微信进程,把它的画面 + 输入流共享给多个 web 客户端。** + +这不是多租户、也不是多开同一账号,而是**一个进程、多个观察/操作者**。这个认知决定了整个架构:核心是"共享屏幕串流",而不是"会话隔离"。 + +懒猫微服的云微信也是同思路:**官方原版客户端不做任何修改 + 容器化 + 内网穿透**,额外加了"陌生设备访问需管理员一次性验证码"。本方案沿用此路线。 + +> 说明:懒猫宣传的"无限多开"是指**多个不同账号各跑一个容器**,不是一个账号多端独立登录。单账号仍是单会话共享。 + +--- + +## 2. 为什么不用「虚拟机 + 局部远程桌面」 + +- **资源浪费**:飞牛 NAS 多为 N100 等低功耗 x86,全量 VM + 完整桌面开销大。 +- **脆弱**:抓窗口坐标做"局部远程桌面",分辨率/缩放一变就裂。 +- **多余**:飞牛 OS 本身是 **Debian 系**,Docker 是一等公民,无需 VM 这层抽象。 + +结论:**容器化 + 虚拟显示 + 串流**,比 VM 方案更轻、更稳、更易分发。 + +--- + +## 3. 推荐架构 + +``` +┌─────────────────────────────────────────────────┐ +│ Docker 容器 (Debian base, amd64 / arm64 自适应) │ +│ │ +│ 微信 Linux 原生版 (官方 .deb, 4.0, 按架构自动选) │ +│ │ 渲染到 │ +│ Xvfb 虚拟显示 :99 (+ 轻量 WM, 如 openbox) │ +│ │ 被串流 │ +│ 串流服务 (KasmVNC / Xpra-html5) │ +│ │ WebSocket │ +└─────────┼──────────────────────────────────────────┘ + │ + 反向代理 + 鉴权 + TLS (Caddy / nginx) + │ + ┌──────┴───────┬───────────────┐ + web 用户A web 用户B web 用户C + (三人看同一画面、可操作同一会话) + + 数据卷挂载: ~/xwechat 等 → 持久化登录态与消息 +``` + +--- + +## 4. 组件选型 + +### 4.1 微信客户端 —— 官方 Linux 原生版 + +- 来源:(2024-11 官方上线) +- 架构:x86_64 / arm64 / LoongArch,提供 deb / rpm / AppImage +- **本项目按构建机架构自动选包**:Dockerfile 用 BuildKit 的 `TARGETARCH` 判断,amd64→`WeChatLinux_x86_64.deb`,arm64→`WeChatLinux_arm64.deb`。这样 x86 NAS、arm NAS、Apple Silicon Mac 本地调试都是同一条 `docker compose up --build`,无需改配置 +- 版本:4.0,功能与 Windows/Mac 4.0 对齐 +- **不用 Wine 转译版**:原生版稳定,且和懒猫一样"不改客户端、合法、不易封号" + +### 4.2 虚拟显示 —— Xvfb + +- 无头环境提供 X11 显示(`:99`) +- 配一个极轻量窗口管理器(openbox/fluxbox),让微信窗口能正常 maximize/管理 + +### 4.3 串流层 —— KasmVNC(一期)/ Xpra(二期优化) + +| 维度 | **Xpra** (+ xpra-html5) | **KasmVNC** | +|------|--------------------------|-------------| +| 呈现 | 单窗口 seamless(浏览器里只显示微信窗口,像独立 App) | 带 WM 的完整桌面 | +| 多客户端共享 | ✅ sharing 模式(服务端+客户端均需开启) | ✅ 原生多用户 | +| 会话持久化 | ✅ 进程不掉,可 detach/reattach | ✅ | +| HTML5 客户端 | ✅ 自带 | ✅ 自带 | +| 图像性能 | 好 | 很好 | +| 调试难度 | seamless + HTML5 配置略繁琐 | 简单、稳 | + +**决策:一期先用 KasmVNC 把链路跑通,二期再考虑换 Xpra 优化"独立 App"观感。** +理由:KasmVNC 稳、配置少,能最快验证"扫码登录 + 收发消息 + 多端共享"这条核心链路;Xpra seamless 模式观感更好但调起来更费劲,留到核心稳定后再做。 + +### 4.4 反向代理 + 鉴权 + TLS —— Caddy 或 nginx + +见第 5 节安全设计(**这是整套方案最关键的一环**)。 + +### 4.5 数据持久化 —— Docker volume + +- 挂载微信数据目录(如 `~/xwechat`、`~/.config` 相关路径)为命名卷 +- 否则每次重启容器都要重新扫码登录 + +--- + +## 5. 安全设计(最关键,必须做) + +⚠️ 这套系统暴露的是一个**已登录的微信**:任何能访问该端口的人都能看到全部聊天记录、并以你的身份发消息。安全是第一优先级,不是可选项。 + +必须满足: + +1. **强制 TLS**:全程 HTTPS/WSS,不允许明文。 +2. **独立鉴权层**:不要只靠裸 VNC 密码。在反向代理上加独立登录(如 Caddy forward_auth / Authelia,或飞牛系统用户体系)。 +3. **默认不暴露公网**: + - 一期只在内网访问; + - 远程访问走飞牛 FN Connect / VPN / 内网穿透; +4. **陌生设备二次验证**(可选,学懒猫):新设备首次访问需管理员一次性验证码。 +5. **最小权限容器**:非 root 运行微信进程,限制容器能力。 + +--- + +## 6. 两个必须处理的工程细节 + +### 6.1 并发输入冲突 + +多人同时操作同一会话,键鼠会互相打架。 + +- **方案**:加一层"控制权令牌"——同一时刻仅一人可操作,其余人只读;支持抢占/移交;UI 显示当前控制者。 +- 一期可先不做(自己/家人小范围用),但要在设计上预留。 + +### 6.2 掉登录 → 重扫码 + +微信桌面会话会定期失效,需手机重新扫码。 + +- 持久化数据卷能**降低**重扫频率,但不能消除。 +- 需设计一个入口:当微信弹出二维码时,让管理员能在 web 端看到并用手机重扫。 + +--- + +## 7. Docker vs 飞牛原生 fpk + +| | Docker(一期) | fpk 原生包(后期) | +|---|---|---| +| 飞牛支持 | 一等公民(Docker/Compose) | 原生 App 形态,进应用中心 | +| 开发效率 | 高,好调试好更新 | 需按 [fpk 框架](https://developer.fnnas.com/docs/core-concepts/framework) 适配 | +| 系统集成 | 一般 | 好(用户体系、分发) | +| 本质 | —— | 仍是包装同一容器/进程 | + +**策略:一期纯 Docker(`docker-compose.yml`)跑通核心;待稳定后再包 fpk 做分发层。不要一开始就上 fpk。** + +--- + +## 8. 计划目录结构(一期 Docker) + +``` +WechatOnCloud/ +├── 技术方案.md # 本文档 +├── docker/ +│ ├── Dockerfile # Debian + Xvfb + 微信deb + KasmVNC +│ ├── entrypoint.sh # 启动 Xvfb → WM → 微信 → KasmVNC +│ └── supervisord.conf # 进程守护(可选) +├── docker-compose.yml # 服务编排 + 卷 + 端口 +├── caddy/ +│ └── Caddyfile # 反向代理 + TLS + 鉴权 +└── README.md +``` + +--- + +## 9. 落地路线图 + +1. **跑通核心链路**:Debian + Xvfb + 官方微信 deb + KasmVNC,浏览器能扫码登录、收发消息。 +2. **加固**:反向代理 + 独立鉴权 + TLS + 数据卷持久化。 +3. **多端共享**:验证多 web 端共享同一会话 + 加并发控制锁。 +4. **观感优化(可选)**:换 Xpra 改为单窗口 App 体验。 +5. **分发(可选)**:包成飞牛 fpk。 + +--- + +## 10. 已知风险 / 待验证 + +- **微信反无头/反虚拟化检测**:原生 Linux 版一般无问题(登录门槛在手机扫码),需实测确认。 +- **资源占用**:微信 Linux 原生版偏重 + 串流编码吃 CPU。NAS 无 GPU,需用 CPU 友好的编码设置(KasmVNC 较省)。单实例 N100 级应可承受,需压测。 +- **封号风险**:官方不改客户端理论上合法,但"服务器常驻 + 多端"是否触发风控需观望(懒猫宣称"永不封号",仅作参考,不保证)。 +- **重扫码频率**:实际失效周期需观察。 + +--- + +## 11. 参考链接 + +- [微信 Linux 官网](https://linux.weixin.qq.com/en) +- [腾讯上线 Linux 微信官网(x86/Arm/LoongArch)](https://finance.sina.com.cn/tech/roll/2024-11-06/doc-incvceuc0172323.shtml) +- [懒猫微服 云微信](https://lazycat.cloud/lcmd/) +- [开源云微信部署方案(低配版懒猫远程微信)](https://linux.do/t/topic/1306818) +- [Xpra 官方文档](https://xpra.org/manual) +- [Xpra HTML5 客户端](https://github.com/Xpra-org/xpra-html5) +- [飞牛 fpk 框架文档](https://developer.fnnas.com/docs/core-concepts/framework)