Repository navigation
Releases: ptonlix/MercLink
Release list
v0.3.1
v0.3.1
日期:2026-10-20
这是 v0.3.0 之后的店面说明。设备码、授权范围、买家注册、买入开关、支付成功条件、商家问答、商品描述的读写和长度、店面缓存、文本压缩范围、Caddy 匹配和安装回滚都没有改。没有新路由,没有新错误码,也没有新依赖。没有新的 SQL 文件。compose.yaml 与 v0.3.0 相同。install.sh 与 v0.3.0 相同。
v0.3.0 的店面渲染器已经会填 product.description。内置商品页和起始模板也已经声明这个槽位。/storefront/skill.md 的事实槽位清单当时停在名称、封面、字段、规格、库存、可售状态和价格。照着 Skill 改页面的人会把某一件商品的描述写进 HTML,或者一直不声明槽位,商品页正文就保持空白。这一版只把这个可选槽位写进店面 Skill,让说明和运行时一致。
生产应用镜像是 docker.io/<用户名>/merclink:0.3.1。正式版同时推送 0.3.1 和 latest。要换应用镜像,店面 Skill 才会变成这一版的正文。不需要换 compose.yaml。已经在 v0.3.0 上的机器没有新迁移要跑。
中文
这一版能做什么
/storefront/skill.md的事实槽位清单增加空元素<merclink-slot name="product.description"></merclink-slot>。- 这个槽位可以不声明。没有它的发布仍可激活。服务端不改写已发布模板,也不会自动补上描述。
- 声明在商品页时,服务端填入当前商品自己的纯文本描述。内部换行予以保留。描述为空时槽位为空。不要把某一件商品当时的描述写进 HTML,也不要另起槽位名。
- 首页和商品列表不必声明它。它不是商品页或商品列表的必填槽。必填商品槽仍是名称、封面、价格和可售状态,再加上列表的
products.next,以及商品页的字段和规格。 - 常见错误用法补了两条:把某一件商品的描述直接写进 HTML;自创一个描述槽位名,或以为旧版 Skill 没写的
product.description不能发布。 - 买家 Skill 和商家 Skill 没有新增路径,也没有改描述字段的说明。
这一版不做
- 不改商品描述的存储、写入、长度、搜索、Markdown 或 JSON-LD。
- 不把
product.description变成必填槽。缺少这个槽位时,发布校验仍然接受模板。 - 不改写已经激活的自定义店面。页面不会因此多出一段描述。商家要在模板里声明这个槽位后再发布,自定义店面才会显示描述。
- 不改内置商品页,也不改起始模板。这两处在 v0.3.0 里已经声明了
product.description。 - 不改商家问答。全店和商品问答仍是 v0.3.0 的两份资源。
GET /api/v1/store在介绍未发布、全店问答已公开时,仍可以只返回{ faqs }。 - 不改「如何使用 MercLink」。
- 不改设备码流程、scope、买家注册或支付成功条件。只有订单状态
paid表示支付成功。调用方仍不能传价格。 - 不改店面 HTML 的
no-store,也不改 CSS、JS 的 10 分钟新鲜期和之后最多 24 小时的后台校验。 - 不改公开图片按 id 缓存一年的规则。缺失图片仍是
404。 - 不改 Caddy 的五行
Content-Type匹配,也不改镜像标签。仍是caddy:2.11.7-alpine。 - 不改一键安装询问的字段,不安装系统包,也不默认打开买入。安装和升级未完成时的回滚与 v0.3.0 相同。
- 不新增路由、错误码、依赖或迁移。不激活店面,不补写历史发布,也不把 Docker Hub 用户名、令牌、域名或证书写进仓库。
升级注意
这一版的变化在应用镜像里的店面 Skill。compose.yaml 和 install.sh 与 v0.3.0 相同。不要删 .env,也不要删 caddy_data。不要改已经应用过的迁移。这一版没有新的 SQL 文件。
从 v0.3.0 升上来时,数据库不再前进一步。从 v0.2.6 直接升到这一版时,进程启动仍会补上 v0.3.0 的 018_merchant_faqs.sql。那次迁移只建 merchant_faqs 和两个唯一索引,不回填。已经跑过 018 的库不会再执行一遍。
一键安装过的机器,等这个正式版发布后执行:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade 会下载这个 tag 的 compose.yaml,保留已有密钥和后来手工打开的买入配置,然后 pull。它不打印已保存的密码。这次 compose.yaml 没有变化。升级如果没有完成,部署文件和版本号会回到升级前;已经换上的容器要再执行 docker compose up -d 才和文件一致。
手工部署的机器不要再跑一键安装。把 MERCLINK_IMAGE 改为 docker.io/<用户名>/merclink:0.3.1,然后执行:
docker compose pull && docker compose up -d不要加 --build。拉取完成且进程起来之后,公开页与 v0.3.0 一致。/storefront/skill.md 会列出 product.description。已经激活的自定义店面不会因此改版。还没有接受发布时,起始店面仍是 v0.3.0 那一份,商品页里本来就有这个槽位。
回滚时把应用镜像改回 docker.io/<用户名>/merclink:0.3.0,再 pull。不要把 latest 当作回滚点。latest 会在这个正式版发布时移到 0.3.1。回到 0.3.0 只是让店面 Skill 重新不列出这个槽位。0.3.0 的渲染器在模板声明了 product.description 时仍然会填描述。没有新表需要保留或删除。v0.3.0 已经建成的 merchant_faqs 继续留着;删掉会丢掉那一版写入的问答。
English
Date: 2026-10-20
This is a storefront-skill correction after v0.3.0. The device-code flow, authorization scopes, buyer registration, purchase switch, the rule that only paid means payment success, merchant FAQs, how product descriptions are stored and how long they may be, storefront caching, the set of compressed types, the Caddy matchers, and install rollback are unchanged. There is no new route, no new error code, and no new dependency. There is no new SQL file. compose.yaml is the same file as v0.3.0. install.sh is the same file as v0.3.0.
The v0.3.0 storefront renderer already fills product.description. The built-in product page and the starter template already declare that slot. The fact-slot list in /storefront/skill.md stopped at the name, cover, fields, variants, stock, availability, and price. Someone following the Skill would hard-code one product's description into HTML, or never declare the slot, and the product page body would stay blank. This version only writes that optional slot into the storefront Skill, so the document matches the runtime.
The production image is docker.io/<namespace>/merclink:0.3.1. The stable release also publishes latest. The application image has to be replaced before the storefront Skill shows this text. compose.yaml does not need to change. A host already on v0.3.0 has no new migration to apply.
What this version does
- The fact-slot list in
/storefront/skill.mdadds the empty element<merclink-slot name="product.description"></merclink-slot>. - The slot may be omitted. A release without it can still be activated. The server does not rewrite a published template, and it does not insert a description on its own.
- When the product page declares the slot, the server fills the current product's own plain-text description. Line breaks inside that description are preserved. An empty description leaves the slot empty. Do not write one product's description into the HTML, and do not invent another slot name.
- The home page and the product list do not need to declare it. It is not a required slot on the product page or the product list. The required product slots stay the name, cover, price, and availability, plus
products.nexton the list, and the fields and variants on the product page. - The common-mistakes list adds two cases: writing one product's description straight into HTML, and inventing a description slot name, or treating
product.descriptionas unpublishable because an older Skill omitted it. - The buyer Skill and the merchant Skill do not gain a path, and their description-field text is unchanged.
What this version does not do
- It does not change how a product description is stored or written, its length rule, search, Markdown, or JSON-LD.
- It does not make
product.descriptionrequired. Publish validation still accepts a template that omits the slot. - It does not rewrite an already active custom storefront. The page does not grow a description on its own. The merchant declares the slot and publishes again before a custom storefront shows the text.
- It does not change the built-in product page or the starter template. Both already declared
product.descriptionin v0.3.0. - It does not change merchant FAQs. The store FAQ and the product FAQ stay the two resources from v0.3.0. When the profile is unpublished and the store FAQ is public,
GET /api/v1/storecan still return only{ faqs }. - It does not change “如何使用 MercLink”.
- It does not change the device-code flow, scopes, buyer registration, or the payment-success rule. Only order status
paidmeans payment succeeded. The caller still cannot send a price. - It does not change
no-storeon storefront HTML, or the 10-minute freshness window and later 24-hour background revalidation for CSS and JS. - It does not change the one-year cache for a public image id. A missing image is still
404. - It does not change Caddy's five
Content-Typelines or the image tag. It remainscaddy:2.11.7-alpine. - It does not change what one-click install asks for, it does not install system packages, and it does not turn purchase on by default. Rollback of an unfinished install or upgrade stays as it was in v0.3.0.
- It does not add a route, an error code, a dependency, or a migration. It does not activate a storefront, backfill old releases, or store a Docker Hub username, token, domain, or certificate in the repository.
Upgrade note
The change in this version is the storefront Skill inside the application image. compose.yaml and install.sh match v0.3.0. Do not delete .env or caddy_data. Do not edit a migration that has already been applied. This version adds no SQL file.
An upgrade from v0.3.0 does not move the database forward. An upgrade that jumps from v0.2.6 to this version still applies v0.3.0's 018_merchant_faqs.sql at process startup. That migration only creates merchant_faqs and two unique indexes. It does not backfill. A database that has already applied 018 does not run it again.
After this stable release is published, a host installed by the script should run:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade downloads compose.yaml for this tag, keeps generated secrets and any later purchase settings, then pulls. It does not print stored passwords. This compose.yaml is unchanged. If this upgrade does not finish, the deploy files and version pin return to their previous values. Containers already recreated from the new image match those files only after another docker compose up -d.
Do not run a fresh one-click install over a deployment that was created by hand. Set MERCLINK_IMAGE to docker.io/<namespace>/merclink:0.3.1, then run:
docker compose pull && docker compose up -dDo not add --build. After the pull, once the process is up, public pages stay as they were in v0.3.0. /storefront/skill.md lists product.description. An already active custom storefront is not republished by this upgrade. When no release has been accepted, the starter is still the v0.3.0 copy, and its product page already declares this slot.
To roll back, set the application image to docker.io/<namespace>/merclink:0.3.0 and pull again. Do not use latest as the rollback point. latest moves to 0.3.1 when this stable release is published. Returning to 0.3.0 only removes this slot from the storefront Skill. The v0.3.0 renderer still fills product.description when the template declares it. There is no new table to keep or to drop. Leave merchant_faqs in place if v0.3.0 created it. Dropping that table discards FAQs written by that version.
v0.3.0
v0.3.0
日期:2026-10-19
这是 v0.2.6 之后的商家问答。设备码、授权范围、买家注册、买入开关、支付成功条件、商品描述、店面缓存、文本压缩范围、Caddy 匹配和安装回滚都没有改。没有新错误码,也没有新依赖。compose.yaml 与 v0.2.6 相同。install.sh 与 v0.2.6 相同。
v0.2.6 的公开页没有店家可维护的问答。把问答写进店铺介绍或商品描述,会在改店名、改简介或上下架时顺带发布或清掉。这一版把问答做成两份独立资源:全店一份,每件商品一份。平台不预置问题。保存介绍、上架或下架商品都不改问答行。
新增四条商家路由:GET 和 PUT /api/v1/merchant/faqs,以及 GET 和 PUT /api/v1/catalogs/{id}/products/{product_id}/faqs。公开读取不另开 FAQ 路径。GET /api/v1/store 的形状变了:介绍未发布但全店问答已公开时,可以只返回 { faqs }。调用方不能再把这个 200 单独理解成介绍已发布。
生产应用镜像是 docker.io/<用户名>/merclink:0.3.0。正式版同时推送 0.3.0 和 latest。要换应用镜像。进程启动时会执行新迁移 018_merchant_faqs.sql。不需要换 compose.yaml。
中文
这一版能做什么
- 新表
merchant_faqs。一个商家的全店问答一行,product_id为空;一件商品的问答一行,product_id指向该商品。published是布尔,items是只含question和answer的 JSON 数组。全店按商家唯一,商品按product_id唯一。不回填,不插入默认问题。软删除商品不删这一行。 - 两条
PUT都是整份替换,正文必须是application/json。published和items都必须出现,不能多字段。还没保存过时,GET返回published: false、items: []、updated_at: null,不插入行,也不是404。写入要商家令牌,并且要有product:write。没有新增 FAQ scope。买家或缺少product:write仍是forbidden。目录不拥有该商品,或商品已软删除,商品问答是not_found。 - 问题和答案必须是单行纯文本。换行、控制字符、HTML 和 Markdown 直接拒绝,不悄悄删掉。先去掉首尾空白,再用
Array.from量长度。问题 1 到 80 个字符,答案 1 到 400 个字符。全店最多 12 条,商品最多 8 条。同一份里去掉空白后相同的问题拒绝。published: true且条目为空时拒绝,并且不写库。published: false且条目为空会清空,公开页不再显示。登录手机号和账号邮箱不能出现在问题或答案里。拒绝仍是validation_error。 faqs成为保留系统字段名,和title、description、price、stock一样。不能新建这个目录字段 key。介绍PUT、商品创建和商品PATCH带上faqs也是validation_error,不会把问答写进fields。商家商品 JSON 不增加faqs。要读写问答,走上面的四条路由。- 全店问答只看自己的
published。介绍已发布时,GET /api/v1/store原有字段都在,并附加faqs;未公开或没有条目时faqs为[]。介绍未发布但全店问答已公开且非空时,200的data只有faqs,不带回介绍草稿。两者都不可见,或商家已停用、已删除时,仍是not_found。 - 公开商品详情附加已公开的
faqs。未公开时这个数组是[]。商品还要仍在公开查询结果里:已上架、未软删除,且商家有效。下架或软删除后,公开页不再显示该商品问答;商家侧的条目和published保留。重新上架后,仍公开的问答回到详情。商品列表、首页卡片和关键词q不带问答正文,也不按问答搜索。 - 内置首页的顺序是店头、商品货架、全店问答、联系方式和执照、使用指南。内置商品详情把「查看用户 Skill」留在商品信息下面。商品问答在详情正文之后单独占一整行。有可见条目时标题是一个
h2「常见问题」。问题用h3,答案转义后放在同一条 HTML 里。默认全部收起,同一页同时只展开一条,不依赖脚本。没有可见条目时不渲染这块。 /index.md和/products/{id}.md使用同一份已公开条目。有条目时单独成## 常见问题,空则省略。商品列表 Markdown 和/llms.txt不展开问答正文。有可见条目时另加一段FAQPageJSON-LD,不替换现有OnlineStore或Product。- 店面用重复区域
data-merclink="store.faq"和data-merclink="product.faq"。区域里只许faq.question和faq.answer。这两个槽位出现在区域外,或区域里再放别的槽位,模板不能发布。有可见条目时,服务端在该区域条目前插入恰好一个<h2>常见问题</h2>。没有条目则去掉区域,也不留标题。起始模板不写死这个标题。已经接受、而且没有这些区域的自定义店面保持原样,页面不会自动补问答。商家要在模板里声明区域后再发布,自定义店面才会出现这段文字。商家 Skill 和店面 Skill 已改成这套说明。买家 Skill 不新增管理路径。
这一版不做
- 不把问答放进
merchant_profiles或products的列,也不写入目录fields。 - 不把全店问答自动附到每件商品上,不预置问题,不参与关键词搜索。
- 不把问答放进首页卡片、商品列表或店铺简介。
- 不改「如何使用 MercLink」。
- 不渲染问答里的 Markdown 或 HTML。问题里的换行不会被收成空格,而是直接拒绝。
- 不改商品描述的读写、长度和
product.description槽位。 - 不改设备码流程、scope、买家注册或支付成功条件。只有订单状态
paid表示支付成功。调用方仍不能传价格。写入问答仍用已有的product:write。 - 不改店面 HTML 的
no-store,也不改 CSS、JS 的 10 分钟新鲜期和之后最多 24 小时的后台校验。 - 不改公开图片按 id 缓存一年的规则。缺失图片仍是
404。 - 不改 Caddy 的五行
Content-Type匹配,也不改镜像标签。仍是caddy:2.11.7-alpine。 - 不改一键安装询问的字段,不安装系统包,也不默认打开买入。安装和升级未完成时的回滚与 v0.2.6 相同。
- 不新增错误码或依赖。不激活店面,不补写历史发布,也不把 Docker Hub 用户名、令牌、域名或证书写进仓库。
升级注意
问答在应用镜像和数据库迁移里。compose.yaml 和 install.sh 与 v0.2.6 相同。不要删 .env,也不要删 caddy_data。不要改已经应用过的迁移;新文件只是 018_merchant_faqs.sql。
这次迁移没有目录字段前置检查。它只建 merchant_faqs 和两个唯一索引,不回填。已有商品、介绍和订单不受影响。升级完成后,新建目录字段不能再使用 key faqs。已经存在的同名目录字段不会被这次迁移停用或搬走。
一键安装过的机器,等这个正式版发布后执行:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade 会下载这个 tag 的 compose.yaml,保留已有密钥和后来手工打开的买入配置,然后 pull。它不打印已保存的密码。这次 compose.yaml 没有变化。升级如果没有完成,部署文件和版本号会回到升级前;已经换上的容器要再执行 docker compose up -d 才和文件一致。新容器要能跑完 018 才会正常服务。
手工部署的机器不要再跑一键安装。把 MERCLINK_IMAGE 改为 docker.io/<用户名>/merclink:0.3.0,然后执行:
docker compose pull && docker compose up -d不要加 --build。拉取完成且进程起来之后,公开页与 v0.2.6 一致,直到商家公开一份非空问答。还没有接受发布时,重新下载的起始店面带有 store.faq 和 product.faq。已经激活的自定义店面不会因此改版。
回滚时把应用镜像改回 docker.io/<用户名>/merclink:0.2.6,再 pull。不要把 latest 当作回滚点。latest 会在这个正式版发布时移到 0.3.0。0.2.6 不读 merchant_faqs。迁移已经成功时,不要为了回滚删掉这张表;删掉会丢掉这一版写入的问答。介绍、商品、订单金额、库存和支付状态不依赖这张表。
English
Date: 2026-10-19
This is merchant-managed FAQs after v0.2.6. The device-code flow, authorization scopes, buyer registration, purchase switch, the rule that only paid means payment success, product descriptions, storefront caching, the set of compressed types, the Caddy matchers, and install rollback are unchanged. There is no new error code and no new dependency. compose.yaml is the same file as v0.2.6. install.sh is the same file as v0.2.6.
In v0.2.6 the public pages have no FAQ a merchant can maintain. Putting questions into the store profile or a product description would publish or clear them whenever the name, summary, or shelf status changes. This version stores two separate resources: one for the shop, and one for each product. The platform does not insert default questions. Saving the profile, or publishing and unpublishing a product, does not change an FAQ row.
Four merchant routes are new: GET and PUT /api/v1/merchant/faqs, and GET and PUT /api/v1/catalogs/{id}/products/{product_id}/faqs. Public reads do not gain an FAQ-only path. The shape of GET /api/v1/store changes: when the profile is unpublished and the store FAQ is public, the response can be only { faqs }. A caller can no longer treat that 200 by itself as proof that the profile is published.
The production image is docker.io/<namespace>/merclink:0.3.0. The stable release also publishes latest. The application image has to be replaced. Process startup applies the new migration 018_merchant_faqs.sql. compose.yaml does not need to change.
What this version does
- A new table,
merchant_faqs. One row is a merchant's store FAQ, withproduct_idnull. One row is a product FAQ, withproduct_idpointing at that product.publishedis a boolean.itemsis a JSON array ofquestionandansweronly. The store row is unique per merchant. The product row is unique perproduct_id. Nothing is backfilled, and no default question is inserted. Soft-deleting a product does not delete the row. - Both
PUTroutes replace the whole document. The body must beapplication/json.publishedanditemsare both required, and extra fields are rejected. Before any save,GETreturnspublished: false,items: [], andupdated_at: null. It does not insert a row, and it is not404. A write needs a merchant token withproduct:write. There is no new FAQ scope. A buyer, or a token withoutproduct:write, is stillforbidden. A product FAQ for a product the catalog does not own, or a soft-deleted product, isnot_found. - Questions and answers must be single-line plain text. Line breaks, control characters, HTML, and Markdown are rejected rather than stripped. End whitespace is removed first, then length is
Array.from(...).length. A question is 1 to 80 characters. An answer is 1 to 400 characters. A store FAQ has at most 12 items. A product FAQ has at most 8. Two questions that match after trimming are rejected.published: truewith an empty list is rejected and is not written.published: falsewith an empty list clears the set, and the public page stops showing it. The sign-in phone number and the account email cannot appear in a question or an answer. Rejection staysvalidation_error. faqsis now a reserved system field name, liketitle,description,price, andstock. It cannot be created as a catalog field key. A profilePUT, product create, or productPATCHthat includesfaqsis alsovalidation_errorand does not store the FAQ infields. Merchant product JSON does not grow afaqsfield. Read and write the FAQ through the four routes above.- A store FAQ is visible only from its own
publishedflag. When the profile is published,GET /api/v1/storekeeps the existing fields and addsfaqs. That array is[]when the FAQ is unpublished or empty. When the profile is unpublished and the store FAQ is public and non-empty,200datais onlyfaqs, with no profile draft. When neither is visible, or the merchant is disabled or deleted, the response staysnot_found. - A public product detail adds the published
faqs. The array is[]when the FAQ is not public. The product must also still be in the public query: on the shelf, not soft-deleted, and owned by an active merchant. After unpublishing or soft-deleting, the public page hides that product FAQ. The merchant copy andpublishedflag remain. Publishing the product again brings a still-public FAQ back to the detail page. The product list, home cards, and keywordqdo not include FAQ text, and search does not match it. - Built-in home order is the store header, the product shelf, the store FAQ, contact and license, then the usage guide. Built-in product detail keeps “查看用户 Skill” under the product facts. The product FAQ is its own full-width row after that detail. When items are visible, the heading is one
h2, “常见问题”. Each question is anh3, and the escaped answer stays in the same HTML. Every item starts collapsed, only one item on a page is open, and this does not depend on script. With no visible items, the block is not rendered. /index.mdand/products/{id}.mduse the same published items. A non-empty set is its own## 常见问题section. An empty set omits the section. Product-list Markdown and/llms.txtdo not expand the FAQ text. A visible set also adds oneFAQPageJSON-LD document and does not replace the existingOnlineStoreorProductdocument.- Storefront templates use repeating regions
data-merclink="store.faq"anddata-merclink="product.faq". Inside a region onlyfaq.questionandfaq.answerare allowed. Either slot outside its region, or any other slot inside the region, makes the template unpublishable. Wh...
v0.2.6
v0.2.6
日期:2026-10-16
这是 v0.2.5 之后的可选商品描述。设备码、授权范围、买家注册、买入开关、支付成功条件、店面缓存、文本压缩范围、Caddy 匹配和安装回滚都没有改。没有新路由,也没有新错误码。compose.yaml 与 v0.2.5 相同。install.sh 与 v0.2.5 相同。
v0.2.5 的商品只有标题、状态、封面和目录字段。公开详情上的说明是用标题、价格和库存拼出来的,商家不能写一段给买家看的正文。这一版给商品增加可选纯文本 description。创建和 PATCH 可以写它。公开详情、商品 Markdown、meta description、JSON-LD 和关键词 q 使用同一份值。新订单行另存下单时的副本。已有商品和已有订单不回填。
同一时间仓库文档也重写了:README.md、README_EN.md、架构图,以及 txpearl.cn、eileenpeng.com 的案例。这些文件不进运行行为。只换文档不会带上商品描述。
生产应用镜像是 docker.io/<用户名>/merclink:0.2.6。正式版同时推送 0.2.6 和 latest。要换应用镜像。进程启动时会执行新迁移 017_product_description.sql。不需要换 compose.yaml。
中文
这一版能做什么
products增加可空列description。纯文本,先去掉首尾空白,内部换行保留。长度用字符串length,1 到 2000 个字符。空字符串、非字符串和超长都是validation_error。省略后为空,空着仍可上架。JSON、页面和订单快照里的空描述都是null,不写进fields。description成为保留系统字段名,和title、price、stock一样。不能放进商品fields,也不能新建、停用或复用这个目录字段 key。拒绝仍是validation_error,不是新错误码。PATCH省略description不改已保存的值;传null清空。省略fields仍不改自定义属性。不能为了改描述去重传fields,也不能用描述改价格。- 公开商品 JSON 和商家商品 JSON 都在
title旁返回description。没有描述时是null。不要从fields读这个键。GET /api/v1/products的q同时匹配标题、这段描述和目录文本字段,不要求catalog_id。描述不参与数字、单选或是否过滤。买家 Skill、未开启买入时的 Skill 和商家 Skill 已改成这套读写说明。 - 内置商品详情在价格和可售状态下面展示这段文字。换行保留,内容转义,不渲染 Markdown 或 HTML。没有描述就不留空块。首页卡片和商品列表仍只显示封面、标题和价格。商品 Markdown 把描述单独成段,不压成一行。
- 有描述时,商品页 meta description 和 JSON-LD
description先把连续空白收成一个空格,再截到 150 个字符。没有描述时仍用标题、价格和库存状态拼成的句子。不另存 SEO 文案。 - 新订单行写入
order_items.description_snapshot,从下单时锁定的商品行复制。当时为空就写null,不放进fields_snapshot。之后修改商品描述不改这一行。旧订单行保持null,不回填。订单 JSON 仍只返回行的id、variant_id、qty和amount,不返回这段快照。 - 内置商品页和起始店面的商品页声明槽位
product.description。槽位有值时转义后输出,换行变成<br>;没有描述时槽位为空。首页和商品列表模板不声明这个槽位。已经接受的发布不会被改写。已发布且没有这个槽位的自定义店面保持原样,页面不会自动补一块描述。商家要在模板里声明product.description后再发布,自定义店面才会出现这段文字。 - 仓库首页改成带首图的说明,并增加英文 README、架构图和两个在线店铺案例。Mermaid 总览改放在
docs/ARCHITECTURE.md。这只影响仓库阅读,不影响已部署进程。
这一版不做
- 不渲染描述里的 Markdown 或 HTML,不加后台表单,也不按规格写描述。
- 不把描述放进首页卡片、商品列表或店铺简介。
- 不自动把已有
fields.description搬进新列,也不停用那个 key。 - 不改设备码流程、scope、买家注册或支付成功条件。只有订单状态
paid表示支付成功。调用方仍不能传价格。 - 不改店面 HTML 的
no-store,也不改 CSS、JS 的 10 分钟新鲜期和之后最多 24 小时的后台校验。 - 不改公开图片按 id 缓存一年的规则。缺失图片仍是
404。 - 不改 Caddy 的五行
Content-Type匹配,也不改镜像标签。仍是caddy:2.11.7-alpine。 - 不改一键安装询问的字段,不安装系统包,也不默认打开买入。安装和升级未完成时的回滚与 v0.2.5 相同。
- 不新增路由、错误码或依赖。不激活店面,不补写历史发布,也不把 Docker Hub 用户名、令牌、域名或证书写进仓库。
升级注意
商品描述在应用镜像和数据库迁移里。compose.yaml 和 install.sh 与 v0.2.5 相同。不要删 .env,也不要删 caddy_data。不要改已经应用过的迁移;新文件只是 017_product_description.sql。
升级前先查目录字段。只要有一行 product_fields.key = 'description',迁移会抛出 product_fields.key description already exists 并整段回滚,新进程起不来。这一版不复制、也不停用那个 key。先导出值,再用现有字段停用流程去掉该 key,然后再升级。停用可能让缺值商品下架。没有这个 key 时,迁移只给 products 和 order_items 各加一列可空文本,不回填。
一键安装过的机器,等这个正式版发布后执行:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade 会下载这个 tag 的 compose.yaml,保留已有密钥和后来手工打开的买入配置,然后 pull。它不打印已保存的密码。这次 compose.yaml 没有变化。升级如果没有完成,部署文件和版本号会回到升级前;已经换上的容器要再执行 docker compose up -d 才和文件一致。新容器要能跑完 017 才会正常服务。
手工部署的机器不要再跑一键安装。把 MERCLINK_IMAGE 改为 docker.io/<用户名>/merclink:0.2.6,然后执行:
docker compose pull && docker compose up -d不要加 --build。拉取完成且进程起来之后,旧商品的 description 仍是 null,公开页和上架行为与 v0.2.5 一致,直到创建或 PATCH 写入描述。用商家接口写入后,内置商品详情会显示这段文字。还没有接受发布时,重新下载的起始店面商品页带有 product.description。已经激活的自定义店面不会因此改版。
回滚时把应用镜像改回 docker.io/<用户名>/merclink:0.2.5,再 pull。不要把 latest 当作回滚点。latest 会在这个正式版发布时移到 0.2.6。0.2.5 不读这两列。迁移已经成功时,不要为了回滚删掉 products.description 或 order_items.description_snapshot;删掉会丢掉新写入的描述。订单金额、库存和支付状态不依赖这两列。
English
Date: 2026-10-16
This is the optional product description after v0.2.5. The device-code flow, authorization scopes, buyer registration, purchase switch, the rule that only paid means payment success, storefront caching, the set of compressed types, the Caddy matchers, and install rollback are unchanged. There is no new route and no new error code. compose.yaml is the same file as v0.2.5. install.sh is the same file as v0.2.5.
In v0.2.5 a product has a title, status, cover, and catalog fields. The public detail blurb is assembled from the title, price, and stock. A merchant cannot write body text for buyers. This version adds an optional plain-text description. Create and PATCH can set it. The public detail page, product Markdown, meta description, JSON-LD, and keyword q use that same value. A new order line stores a copy from order time. Existing products and existing orders are not backfilled.
The repository docs were rewritten in the same range: README.md, README_EN.md, the architecture figure, and case notes for txpearl.cn and eileenpeng.com. Those files do not change runtime behavior. Replacing only the docs does not ship product descriptions.
The production image is docker.io/<namespace>/merclink:0.2.6. The stable release also publishes latest. The application image has to be replaced. Process startup applies the new migration 017_product_description.sql. compose.yaml does not need to change.
What this version does
productsgains a nullabledescriptioncolumn. The text is plain, end whitespace is removed, and internal line breaks stay. Length is JavaScript stringlength, from 1 to 2000 characters. An empty string, a non-string, and an over-long value arevalidation_error. Omitting it leaves the description empty, and publishing does not require it. An empty description isnullin JSON, on the page, and in the order snapshot. It is not stored infields.descriptionis now a reserved system field name, liketitle,price, andstock. It cannot be placed in productfields, and it cannot be created, retired, or reused as a catalog field key. Rejection staysvalidation_error; there is no new error code. OmittingdescriptiononPATCHleaves the saved value unchanged;nullclears it. Omittingfieldsstill leaves custom attributes unchanged. Do not resendfieldsonly to change the description, and do not use the description to change a price.- Public product JSON and merchant product JSON both return
descriptionbesidetitle. It isnullwhen absent. Do not read this key fromfields.qonGET /api/v1/productsmatches the title, this description, and catalog text fields, and does not requirecatalog_id. The description does not take part in number, single-select, or boolean filters. The buyer Skill, the purchase-disabled Skill, and the merchant Skill now describe this read and write behavior. - The built-in product detail shows the text under the price and availability. Line breaks are kept, the text is escaped, and Markdown or HTML is not rendered. An empty description leaves no empty block. Home cards and the product list still show only the cover, title, and price. Product Markdown keeps the description as its own paragraph and does not collapse it to one line.
- When a description exists, the product meta description and JSON-LD
descriptioncollapse consecutive whitespace to one space, then truncate to 150 characters. Without a description they still use the sentence built from title, price, and stock. No separate SEO copy is stored. - A new order line writes
order_items.description_snapshot, copied from the product row locked at order time. An empty description is stored asnulland is not placed infields_snapshot. A later edit to the product description does not change that line. Old order lines staynulland are not backfilled. Order JSON still returns onlyid,variant_id,qty, andamountfor a line. It does not return this snapshot. - The built-in product page and the starter storefront product page declare
product.description. A filled slot is escaped and line breaks become<br>; an empty description leaves the slot empty. The home and product-list templates do not declare this slot. An accepted release is not rewritten. A published custom storefront that does not declare the slot stays as it is, and the page does not grow a description block on its own. The merchant declaresproduct.descriptionin the template and publishes again before a custom storefront shows the text. - The repository home page is now a hero-led introduction, with an English README, an architecture figure, and two live-store case notes. The Mermaid overview moves to
docs/ARCHITECTURE.md. This changes how the repository reads. It does not change a deployed process.
What this version does not do
- It does not render Markdown or HTML inside a description, add an admin form, or store a description per variant.
- It does not put the description on home cards, the product list, or the store profile.
- It does not copy an existing
fields.descriptioninto the new column, and it does not retire that key. - It does not change the device-code flow, scopes, buyer registration, or the payment-success rule. Only order status
paidmeans payment succeeded. The caller still cannot send a price. - It does not change
no-storeon storefront HTML, or the 10-minute freshness window and later 24-hour background revalidation for CSS and JS. - It does not change the one-year cache for a public image id. A missing image is still
404. - It does not change Caddy's five
Content-Typelines or the image tag. It remainscaddy:2.11.7-alpine. - It does not change what one-click install asks for, it does not install system packages, and it does not turn purchase on by default. Rollback of an unfinished install or upgrade stays as it was in v0.2.5.
- It does not add a route, an error code, or a dependency. It does not activate a storefront, backfill old releases, or store a Docker Hub username, token, domain, or certificate in the repository.
Upgrade note
The description ships in the application image and in a database migration. compose.yaml and install.sh match v0.2.5. Do not delete .env or ...
v0.2.5
v0.2.5
日期:2026-10-15
这是 v0.2.4 之后的起始店面打包修复。公开页面、设备码、授权范围、买家注册、买入开关、支付成功条件、店面缓存、文本压缩范围、Caddy 匹配和安装回滚都没有改。没有新的数据库迁移。compose.yaml 与 v0.2.4 相同。install.sh 与 v0.2.4 相同。
v0.2.4 把 storefront/ 放进了运行镜像,但还没有发布记录时,下载仍要现场生成默认 HTML。生成步骤会执行 scripts/render-starter.mjs,再用 TypeScript 编译页面模板。v0.2.4 的运行镜像没有这个脚本,也没有编译器。异常被收成 50301,正文是「店面下载暂时不可用。」首页仍是内置页。txpearl.cn 上 storefront_releases 是 0 行,所以每次下载都走这里。
这一版在应用构建之后渲染两次,分别得到买入关闭和开启的起始页,再合成 /app/storefront-starter.json。运行镜像没有渲染脚本时,下载按进程已有的买入开关选择其中一份,不再启动渲染脚本。工作目录里仍有脚本时,继续执行脚本,不用这份 JSON。三份样式文件由 Dockerfile 显式复制,不靠 standalone 追踪。已有发布仍下载该发布自己的源码包。发布构建不接收 MERCLINK_PURCHASE,也不为它增加 build-arg。
生产应用镜像是 docker.io/<用户名>/merclink:0.2.5。正式版同时推送 0.2.5 和 latest。只换应用镜像即可。不需要换 compose.yaml。往正在运行的 v0.2.4 容器里补文件没有用,那一版的代码不读这份 JSON。
中文
这一版能做什么
- 构建阶段在
pnpm run build之后复制scripts/render-starter.mjs,再分别以MERCLINK_PURCHASE=0和MERCLINK_PURCHASE=1执行它。这两个赋值只属于构建命令,不写入镜像ENV或ARG。结果合并为storefront-starter.json,里面是"0"和"1"两份六个页面。任一份缺非空页面,构建失败。runner 把该文件复制到/app/storefront-starter.json,不复制渲染脚本。 - runner 在 standalone 复制之后,显式复制
src/app/globals.css、src/public-discovery/public.css和src/app/authorize/authorize.css。 - 工作目录没有渲染脚本时,
GET /api/v1/storefront/source按currentPurchaseEnabled()选择"0"或"1",并继续打包/app/storefront里的accept.mjs、preview.mjs、render-slots.mjs和README.md。两份六个片段都必须是非空字符串,否则仍是50301。 - 工作目录有
scripts/render-starter.mjs时,开发和测试执行该脚本,即使留下了storefront-starter.json也不读它。该 JSON 不提交。 - 已经接受过发布时,下载的仍是该发布保存的源码包。没有激活的发布时,公开页仍是内置页。
这一版不做
- 不把 TypeScript、页面模板源码或
scripts/render-starter.mjs放进运行镜像。 - 不把
MERCLINK_PURCHASE写成镜像ENV或 DockerARG。同一镜像同时带上买入关闭和开启的起始页。 - 不改公开页的店名、货架、二维码和执照尺寸、页脚或规格轴显示。
- 不改设备码流程、scope、买家注册或支付成功条件。只有订单状态
paid表示支付成功。 - 不改店面 HTML 的
no-store,也不改 CSS、JS 的 10 分钟新鲜期和之后最多 24 小时的后台校验。 - 不改公开图片按 id 缓存一年的规则。缺失图片仍是
404。 - 不改 Caddy 的五行
Content-Type匹配,也不改镜像标签。仍是caddy:2.11.7-alpine。 - 不改一键安装询问的字段,不安装系统包,也不默认打开买入。安装和升级未完成时的回滚与 v0.2.4 相同。
- 不改
50301的文案。JSON 或样式缺失时仍返回「店面下载暂时不可用。」已有发布的对象缺失时,文案仍是「店面源码暂时无法读取。」 - 不激活店面,不补写历史发布,也不把 Docker Hub 用户名、令牌、域名或证书写进仓库。
升级注意
修复在应用镜像里。compose.yaml 和 install.sh 与 v0.2.4 相同。不要删 .env,也不要删 caddy_data。不要在 v0.2.4 容器里手工放入 storefront-starter.json。
一键安装过的机器,等这个正式版发布后执行:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade 会下载这个 tag 的 compose.yaml,保留已有密钥和后来手工打开的买入配置,然后 pull。它不打印已保存的密码。这次 compose.yaml 没有变化。升级如果没有完成,部署文件和版本号会回到升级前;已经换上的容器要再执行 docker compose up -d 才和文件一致。
手工部署的机器不要再跑一键安装。把 MERCLINK_IMAGE 改为 docker.io/<用户名>/merclink:0.2.5,然后执行:
docker compose pull && docker compose up -d不要加 --build。拉取完成后,用带 storefront:write 的商家令牌再请求 GET /api/v1/storefront/source。还没有发布记录时,压缩包里应有 accept.mjs 和 static/index.html。
回滚时把应用镜像改回 docker.io/<用户名>/merclink:0.2.4,再 pull。不要把 latest 当作回滚点。latest 会在这个正式版发布时移到 0.2.5。退回 0.2.4 会再次缺少渲染结果,第一次下载又会返回 50301。
English
Date: 2026-10-15
This is the starter storefront packaging fix after v0.2.4. The public pages, device-code flow, authorization scopes, buyer registration, purchase switch, the rule that only paid means payment success, storefront caching, the set of compressed types, the Caddy matchers, and install rollback are unchanged. There is no new database migration. compose.yaml is the same file as v0.2.4. install.sh is the same file as v0.2.4.
v0.2.4 places storefront/ in the runner image, but a download with no accepted release still generates the default HTML at request time. That step executes scripts/render-starter.mjs and compiles the page templates with TypeScript. The v0.2.4 runner has neither the script nor the compiler. The error becomes 50301 with the message 店面下载暂时不可用。 The home page stays on the built-in page. On txpearl.cn, storefront_releases has no rows, so every download takes this path.
This version renders twice after the application build, once for purchase off and once for purchase on, and combines them into /app/storefront-starter.json. When the runner has no renderer script, a download selects one payload with the process purchase switch and does not start the renderer. When that script is still in the working directory, it runs and the JSON is ignored. The three stylesheets are copied by the Dockerfile, not by standalone tracing. A release that has already been accepted is still downloaded as that release's own source archive. The release build does not receive MERCLINK_PURCHASE and does not add a build-arg for it.
The production image is docker.io/<namespace>/merclink:0.2.5. The stable release also publishes latest. Replacing the application image is enough. compose.yaml does not need to change. Copying files into a running v0.2.4 container does nothing. That version does not read this JSON.
What this version does
- After
pnpm run build, the build stage copiesscripts/render-starter.mjsand runs it once withMERCLINK_PURCHASE=0and once withMERCLINK_PURCHASE=1. Those assignments belong only to the build command, not to imageENVorARG. The combinedstorefront-starter.jsonholds a"0"payload and a"1"payload, each with six pages. The build fails if either payload is missing a non-empty page. The runner copies that file to/app/storefront-starter.jsonand does not copy the renderer script. - After the standalone copy, the runner explicitly copies
src/app/globals.css,src/public-discovery/public.css, andsrc/app/authorize/authorize.css. - When the working directory has no renderer script,
GET /api/v1/storefront/sourceselects"0"or"1"withcurrentPurchaseEnabled()and still packsaccept.mjs,preview.mjs,render-slots.mjs, andREADME.mdfrom/app/storefront. Both payloads must contain six non-empty strings; otherwise the download is still50301. - When
scripts/render-starter.mjsis in the working directory, development and tests run that script and do not read a leftoverstorefront-starter.json. That JSON is not committed. - After a release has been accepted, the download remains that release's stored source archive. With no active release, public pages remain the built-in pages.
What this version does not do
- It does not put TypeScript, the template sources, or
scripts/render-starter.mjsin the runner image. - It does not set
MERCLINK_PURCHASEas an imageENVor DockerARG. One image carries both the purchase-off and purchase-on starter pages. - It does not change the public store name, shelf, contact-QR or license size, footer, or variant-axis display.
- It does not change the device-code flow, scopes, buyer registration, or the payment-success rule. Only order status
paidmeans payment succeeded. - It does not change
no-storeon storefront HTML, or the 10-minute freshness window and later 24-hour background revalidation for CSS and JS. - It does not change the one-year cache for a public image id. A missing image is still
404. - It does not change Caddy's five
Content-Typelines or the image tag. It remainscaddy:2.11.7-alpine. - It does not change what one-click install asks for, it does not install system packages, and it does not turn purchase on by default. Rollback of an unfinished install or upgrade stays as it was in v0.2.4.
- It does not change the
50301wording. A missing JSON file or stylesheet still returns 店面下载暂时不可用。 A missing object for an accepted release still returns 店面源码暂时无法读取。 - It does not activate a storefront, backfill old releases, or store a Docker Hub username, token, domain, or certificate in the repository.
Upgrade note
The fix is in the application image. compose.yaml and install.sh match v0.2.4. Do not delete .env or caddy_data. Do not place storefront-starter.json into a running v0.2.4 container by hand.
After this stable release is published, a host installed by the script should run:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade downloads compose.yaml for this tag, keeps generated secrets and any later purchase settings, then pulls. It does not print stored passwords. This compose.yaml is unchanged. If this upgrade does not finish, the deploy files and version pin return to their previous values. Containers already recreated from the new image match those files only after another docker compose up -d.
Do not run a fresh one-click install over a deployment that was created by hand. Set MERCLINK_IMAGE to docker.io/<namespace>/merclink:0.2.5, then run:
docker compose pull && docker compose up -dDo not add --build. After the pull, request GET /api/v1/storefront/source again with a merchant token that has storefront:write. When no release row exists, the archive contains accept.mjs and static/index.html.
To roll back, set the application image to docker.io/<namespace>/merclink:0.2.4 and pull again. Do not use latest as the rollback point. latest moves to 0.2.5 when this stable release is published. Returning to 0.2.4 removes the rendered fragments again, and the first download returns 50301.
v0.2.4
v0.2.4
日期:2026-10-15
这是 v0.2.3 之后的店面源码打包修复。公开页面、设备码、授权范围、买家注册、买入开关、支付成功条件、店面缓存、文本压缩范围、Caddy 匹配和安装回滚都没有改。没有新的数据库迁移。compose.yaml 与 v0.2.3 相同。install.sh 与 v0.2.3 相同。
还没有接受过店面发布时,下载接口要打包镜像工作目录里的 storefront/。v0.2.3 的运行镜像只有 Next standalone、静态资源和数据库迁移,这个目录不在里面。打包抛错后返回 50301,正文是「店面下载暂时不可用。」首页仍是内置页,所以网站看起来正常。这一版把仓库里的 storefront/ 复制到 /app/storefront。已有发布仍下载该发布自己的源码包。
生产应用镜像是 docker.io/<用户名>/merclink:0.2.4。正式版同时推送 0.2.4 和 latest。只换应用镜像即可。不需要换 compose.yaml。
中文
这一版能做什么
- 生产镜像的进程工作目录包含
storefront/accept.mjs、preview.mjs、render-slots.mjs和README.md。standalone 文件追踪不包括这个目录,所以 runner 在/app下单独复制它。 .dockerignore不排除storefront/。构建上下文里仍有这个目录。- 还没有
storefront_releases记录时,带storefront:write的GET /api/v1/storefront/source从/app/storefront打包。压缩包里有上述验收和预览脚本,以及由当前默认公开页生成的起始 HTML 和样式。 - 已经接受过发布时,下载的仍是该发布保存的源码包,不是这份起始目录。没有激活的发布时,公开页仍是内置页。
这一版不做
- 不改公开页的店名、货架、二维码和执照尺寸、页脚或规格轴显示。
- 不改设备码流程、scope、买家注册或支付成功条件。只有订单状态
paid表示支付成功。 - 不改店面 HTML 的
no-store,也不改 CSS、JS 的 10 分钟新鲜期和之后最多 24 小时的后台校验。 - 不改公开图片按 id 缓存一年的规则。缺失图片仍是
404。 - 不改 Caddy 的五行
Content-Type匹配,也不改镜像标签。仍是caddy:2.11.7-alpine。 - 不改一键安装询问的字段,不安装系统包,也不默认打开买入。安装和升级未完成时的回滚与 v0.2.3 相同。
- 不改
50301的文案。目录以外的打包失败仍返回「店面下载暂时不可用。」已有发布的对象缺失时,文案仍是「店面源码暂时无法读取。」 - 不激活店面,不补写历史发布,也不把 Docker Hub 用户名、令牌、域名或证书写进仓库。
升级注意
修复在应用镜像里。compose.yaml 和 install.sh 与 v0.2.3 相同。不要删 .env,也不要删 caddy_data。
一键安装过的机器,等这个正式版发布后执行:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade 会下载这个 tag 的 compose.yaml,保留已有密钥和后来手工打开的买入配置,然后 pull。它不打印已保存的密码。这次 compose.yaml 没有变化。升级如果没有完成,部署文件和版本号会回到升级前;已经换上的容器要再执行 docker compose up -d 才和文件一致。
手工部署的机器不要再跑一键安装。把 MERCLINK_IMAGE 改为 docker.io/<用户名>/merclink:0.2.4,然后执行:
docker compose pull && docker compose up -d不要加 --build。拉取完成后,用带 storefront:write 的商家令牌再请求 GET /api/v1/storefront/source。还没有发布记录时,压缩包里应有 accept.mjs。
回滚时把应用镜像改回 docker.io/<用户名>/merclink:0.2.3,再 pull。不要把 latest 当作回滚点。latest 会在这个正式版发布时移到 0.2.4。退回 0.2.3 会再次缺少 /app/storefront,第一次下载又会返回 50301。
English
Date: 2026-10-15
This is the storefront source packaging fix after v0.2.3. The public pages, device-code flow, authorization scopes, buyer registration, purchase switch, the rule that only paid means payment success, storefront caching, the set of compressed types, the Caddy matchers, and install rollback are unchanged. There is no new database migration. compose.yaml is the same file as v0.2.3. install.sh is the same file as v0.2.3.
Before any storefront release is accepted, the download packs storefront/ from the image working directory. The v0.2.3 runner image contains the Next standalone output, static assets, and database migrations, and does not contain that directory. The pack error becomes 50301 with the message 店面下载暂时不可用。 The home page stays on the built-in page, so the site still looks healthy. This version copies the repository storefront/ directory to /app/storefront. A release that has already been accepted is still downloaded as that release's own source archive.
The production image is docker.io/<namespace>/merclink:0.2.4. The stable release also publishes latest. Replacing the application image is enough. compose.yaml does not need to change.
What this version does
- The production image working directory contains
storefront/accept.mjs,preview.mjs,render-slots.mjs, andREADME.md. Standalone file tracing does not include this directory, so the runner copies it into/appon its own. .dockerignoredoes not excludestorefront/. The build context still contains the directory.- When
storefront_releaseshas no row,GET /api/v1/storefront/sourcewithstorefront:writepacks/app/storefront. The archive contains those acceptance and preview scripts, plus the starter HTML and stylesheet generated from the current default public pages. - After a release has been accepted, the download remains that release's stored source archive, not this starter directory. With no active release, public pages remain the built-in pages.
What this version does not do
- It does not change the public store name, shelf, contact-QR or license size, footer, or variant-axis display.
- It does not change the device-code flow, scopes, buyer registration, or the payment-success rule. Only order status
paidmeans payment succeeded. - It does not change
no-storeon storefront HTML, or the 10-minute freshness window and later 24-hour background revalidation for CSS and JS. - It does not change the one-year cache for a public image id. A missing image is still
404. - It does not change Caddy's five
Content-Typelines or the image tag. It remainscaddy:2.11.7-alpine. - It does not change what one-click install asks for, it does not install system packages, and it does not turn purchase on by default. Rollback of an unfinished install or upgrade stays as it was in v0.2.3.
- It does not change the
50301wording. A pack failure outside this missing directory still returns 店面下载暂时不可用。 A missing object for an accepted release still returns 店面源码暂时无法读取。 - It does not activate a storefront, backfill old releases, or store a Docker Hub username, token, domain, or certificate in the repository.
Upgrade note
The fix is in the application image. compose.yaml and install.sh match v0.2.3. Do not delete .env or caddy_data.
After this stable release is published, a host installed by the script should run:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade downloads compose.yaml for this tag, keeps generated secrets and any later purchase settings, then pulls. It does not print stored passwords. This compose.yaml is unchanged. If this upgrade does not finish, the deploy files and version pin return to their previous values. Containers already recreated from the new image match those files only after another docker compose up -d.
Do not run a fresh one-click install over a deployment that was created by hand. Set MERCLINK_IMAGE to docker.io/<namespace>/merclink:0.2.4, then run:
docker compose pull && docker compose up -dDo not add --build. After the pull, request GET /api/v1/storefront/source again with a merchant token that has storefront:write. When no release row exists, the archive contains accept.mjs.
To roll back, set the application image to docker.io/<namespace>/merclink:0.2.3 and pull again. Do not use latest as the rollback point. latest moves to 0.2.4 when this stable release is published. Returning to 0.2.3 removes /app/storefront again, and the first download returns 50301.
v0.2.3
v0.2.3
日期:2026-10-15
这是 v0.2.2 之后的店面和安装回滚版。设备码、授权范围、买家注册、买入开关、支付成功条件、店面缓存、文本压缩范围和 Caddy 匹配都没有改。没有新的数据库迁移。compose.yaml 与 v0.2.2 相同。
没有激活商家店面时,访客看到的是这家店已经发布的名称、简介和商品货架,页脚用一行小字标明平台。商品页上的目录字段和规格轴,在商家已经写过中文名称时显示名称。公开 JSON、商品 Markdown 和支付判断仍用原来的机器字段。
一键安装在 .env 进入安装目录之前取消或失败,不留下这次的配置。升级没有完成时,把 compose.yaml 和版本号还原。已经换成新镜像的容器不会自动退回。生产应用镜像是 docker.io/<用户名>/merclink:0.2.3。正式版同时推送 0.2.3 和 latest。
中文
这一版能做什么
- 商品列表、商品页、找不到商品的页面和接口文档,导航上的店名是已发布的展示名,并链回首页。没有已发布店名时,这里仍是 MercLink。首页没有这行导航,主标题仍是店名槽位;店还没发布时,主标题是空的,不会改成 MercLink。
- 首页顺序是店名和简介、商品货架、客服与执照、使用说明、页脚。货架标题是「商品货架」。每行两张 4:5 的图,图下是名称、价格和「有货」或「缺货」。整张卡片链到商品页。窄屏一行一张,并露出下一张的边。货架下面仍是「查看更多」。没有左右切换按钮。
- 客服二维码显示宽度不超过 180px。执照图不超过 280px。两组事实都没有时,这块不出现。首页 HTML 不再写「商家自行填写,本系统未核验。」
/index.md和/llms.txt仍带这句。 - 页脚在口号下面增加
Powered by MercLink。商品 Markdown、三份 Skill,以及「如何使用 MercLink」这段说明,仍写 MercLink。 - 商品页右侧、规格下面的目录字段标题是「商品信息」。有字段显示名时给人看显示名,否则仍是字段 key。没有公开字段时这一节不出现。商品 Markdown 的标题仍是「公开字段」,并保留字段 key。
- 规格轴已经有名称时,内置商品页写成「名称 取值」,多项用中文逗号隔开。例如轴名是「珠径」时显示「珠径 11-12mm」。没有名称时仍是
key=value。没有规格组合时仍是「默认规格」。缺货仍是红色,有货仍是灰色。 - 已经激活的店面仍用商家上传的 HTML。服务端填
variant.options和product.variants时使用同一套轴名。新增槽位store.nav_name:有已发布店名时是店名,否则是 MercLink。店面 Skill 写明不要拿它当页面主标题。旧发布里没有这个槽位的页面不会因此改掉导航。 - 公开
GET /api/v1/products和单件 JSON 不增加轴名字段,规格仍是option_values。本地预览读的是这份 JSON,所以预览里的规格文字仍是key=value。这一版不补写历史轴名,也不改声明规格轴的接口。 - 第一次安装先把
compose.yaml和.env写在临时目录,再移入安装目录。.env移动成功才算提交。提交前按 Ctrl+C 或失败,删掉这次写下的部署文件;目录是这次新建并且已经空了,也删掉目录。提交之后再取消,保留.env、compose.yaml和数据卷,并提示不要删除.env。 - 升级开始前复制现有
compose.yaml,以及.env里的MERCLINK_IMAGE和MERCLINK_RELEASE_TAG。这份副本权限是700,不含密码和其他配置。下载、替换、拉取或启动没有完成,包括中途 Ctrl+C,都会还原这三项,并删掉半成品compose.yaml.next。不执行compose down,也不自动用旧镜像重建容器。提示里会要求再执行docker compose up -d,容器才会和还原后的文件一致。
这一版不做
- 不改设备码流程、scope、买家注册或支付成功条件。只有订单状态
paid表示支付成功。 - 不改店面 HTML 的
no-store,也不改 CSS、JS 的 10 分钟新鲜期和之后最多 24 小时的后台校验。 - 不改公开图片按 id 缓存一年的规则。缺失图片仍是
404。 - 不改 Caddy 的五行
Content-Type匹配,也不改镜像标签。仍是caddy:2.11.7-alpine。 - 不改一键安装询问的字段,不安装系统包,也不默认打开买入。
- 不在提交
.env之后删除密码或数据卷。不增加卸载命令。 - 不把已经运行的新容器自动换回旧镜像。
latest仍不是版本回滚点。 - 不把 Docker Hub 用户名、令牌、域名或证书写进仓库。升级不打印已保存的密码。
升级注意
页面变化在应用镜像里。安装回滚在这一版的 install.sh 里。compose.yaml 与 v0.2.2 相同;还停在 v0.2.1 那份单行匹配上的机器,仍要换成能通过校验的 Compose,否则 Caddy 起不来。不要删 .env,也不要删 caddy_data。
一键安装过的机器,等这个正式版发布后执行:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade 会下载这个 tag 的 compose.yaml,保留已有密钥和后来手工打开的买入配置,然后 pull。它不打印已保存的密码。这次升级如果没有完成,部署文件和版本号会回到升级前;已经换上的容器要再执行 docker compose up -d 才和文件一致。
手工部署的机器不要再跑一键安装。已经在用 v0.2.2 的 Compose 时,把 MERCLINK_IMAGE 改为 docker.io/<用户名>/merclink:0.2.3,然后执行:
docker compose pull && docker compose up -d不要加 --build。只换镜像可以得到新的公开页,但旧的 install.sh 没有这次的回滚。
回滚页面时,把应用镜像改回带版本号的标签,再 pull。不要把 latest 当作回滚点。latest 会在这个正式版发布时移到 0.2.3。退回 v0.2.1 的 Compose 会再次让 Caddy 退出。要退回旧代理配置,换回那一版能通过校验的 compose.yaml。
English
Date: 2026-10-15
This is the storefront and install-rollback release after v0.2.2. The device-code flow, authorization scopes, buyer registration, purchase switch, the rule that only paid means payment success, storefront caching, the set of compressed types, and the Caddy matchers are unchanged. There is no new database migration. compose.yaml is the same file as v0.2.2.
When no merchant storefront is active, visitors see the published store name, summary, and product shelf. The footer credits the platform in one short line. Catalog fields and variant axes use the merchant's Chinese names on the human product page when those names already exist. Public JSON, product Markdown, and the payment-success rule keep the existing machine fields.
A one-click install that is cancelled or fails before .env is in the install directory leaves no configuration from that attempt. An upgrade that does not finish restores compose.yaml and the version pin. Containers already recreated from the new image are not rolled back automatically. The production image is docker.io/<namespace>/merclink:0.2.3. The stable release also publishes latest.
What this version does
- The product list, product page, missing-product page, and interface docs use the published display name in the nav, linking back to the home page. With no published name, that word is still MercLink. The home page has no nav row. Its title remains the store-name slot. An unpublished store leaves that title empty. It does not become MercLink.
- The home page order is the store name and summary, the product shelf, contact and license facts, the usage guide, then the footer. The shelf heading is 商品货架. Each row shows two 4:5 photos, with the name, price, and 有货 or 缺货 under the photo. The whole card links to the product. A narrow screen shows one photo and a peek of the next. 查看更多 stays under the shelf. There are no previous or next buttons.
- A contact QR is shown no wider than 180px. A license image is shown no wider than 280px. The block is omitted when both fact groups are empty. Home-page HTML no longer includes 商家自行填写,本系统未核验。
/index.mdand/llms.txtstill include that sentence. - The footer adds
Powered by MercLinkunder the slogan. Product Markdown, the three skills, and the “如何使用 MercLink” guide still say MercLink. - Catalog fields on the product page sit under the variants. The heading is 商品信息. A person sees the display label when one exists, and the field key otherwise. The section is omitted when the product has no public fields. Product Markdown still uses the heading 公开字段 and keeps the field key.
- When a variant axis already has a name, the built-in product page shows
name value, joined with a Chinese comma. An axis named 珠径 is shown as珠径 11-12mm. An axis with no name stayskey=value. A variant with no options stays 默认规格. Out-of-stock text stays red. In-stock text stays gray. - An active storefront still serves the merchant's uploaded HTML. The server uses the same axis names when it fills
variant.optionsandproduct.variants. The new slot isstore.nav_name: the published store name, or MercLink when none is published. The storefront skill says not to use it as the page title. A release that does not contain this slot does not change its nav. - Public
GET /api/v1/productsand the single-product JSON do not gain an axis-name field. Variants remainoption_values. Local preview reads that JSON, so previewed option text stayskey=value. This version does not backfill old axis names and does not change the axis-declaration API. - A fresh install writes
compose.yamland.envin a temporary directory, then moves them into the install directory. The attempt is committed only when.envhas been moved. Cancelling with Ctrl+C or failing before that removes the deploy files written by the attempt. A directory created by the attempt is removed when it is empty. After the commit, cancelling keeps.env,compose.yaml, and the data volumes, and tells the operator not to delete.env. - Before changing an existing install, upgrade copies
compose.yamland theMERCLINK_IMAGEandMERCLINK_RELEASE_TAGvalues. The copy is mode700and does not contain passwords or other settings. If download, replacement, pull, or startup does not finish, including on Ctrl+C, those three values are restored and a partialcompose.yaml.nextis removed. Upgrade does not runcompose downand does not recreate containers from the old image. The message tells the operator to rundocker compose up -dbefore the containers match the restored files.
What this version does not do
- It does not change the device-code flow, scopes, buyer registration, or the payment-success rule. Only order status
paidmeans payment succeeded. - It does not change
no-storeon storefront HTML, or the 10-minute freshness window and later 24-hour background revalidation for CSS and JS. - It does not change the one-year cache for a public image id. A missing image is still
404. - It does not change Caddy's five
Content-Typelines or the image tag. It remainscaddy:2.11.7-alpine. - It does not change what one-click install asks for, it does not install system packages, and it does not turn purchase on by default.
- It does not delete passwords or data volumes after
.envis committed. It does not add an uninstall command. - It does not automatically move a running new container back to the old image.
latestis still not a rollback point. - It does not store a Docker Hub username, token, domain, or certificate in the repository. Upgrade does not print stored passwords.
Upgrade note
The page change is in the application image. Install rollback is in this version of install.sh. compose.yaml matches v0.2.2. A host still using the v0.2.1 one-line matcher must replace it with a Compose file that validates, or Caddy will not start. Do not delete .env or caddy_data.
After this stable release is published, a host installed by the script should run:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade downloads compose.yaml for this tag, keeps generated secrets and any later purchase settings, then pulls. It does not print stored passwords. If this upgrade does not finish, the deploy files and version pin return to their previous values. Containers already recreated from the new image match those files only after another docker compose up -d.
Do not run a fresh one-click install over a deployment that was created by hand. On a host that already uses the v0.2.2 Compose file, set MERCLINK_IMAGE to docker.io/<namespace>/merclink:0.2.3, then run:
docker compose pull && docker compose up -dDo not add `--buil...
v0.2.2
v0.2.2
日期:2026-10-14
这是 v0.2.1 的代理启动修复。店、授权范围、公开发现、买入规则、支付成功条件、店面缓存和文本压缩范围都没有改。没有新的数据库迁移。应用代码也没有改。
v0.2.1 的 compose.yaml 让 Caddy 只压缩文本、JSON、XML、JavaScript 和 SVG。这个意图是对的,但五种类型写在同一行 header Content-Type 上。钉死的 caddy:2.11.7-alpine 拒绝这行,入口脚本在 caddy run 时退出,公网 HTTPS 起不来。应用容器本身不受影响。没开 caddy profile 的机器也碰不到这个问题。
这一版只把同一条匹配拆成五行。适配后的配置仍是一个 Content-Type 字段、五个“或”模式,不是五种类型必须同时满足。生产应用镜像是 docker.io/<用户名>/merclink:0.2.2。正式版同时推送 0.2.2 和 latest。只换应用镜像修不好这件事,必须换这一版的 compose.yaml。
中文
这一版能做什么
- 正式 Compose 的 Caddy 可以完成配置校验并开始监听 80 和 443。v0.2.1 在这里失败,日志是
malformed header matcher: expected field。 - 压缩对象仍是响应的
Content-Type,不是请求头。匹配text/*、application/json*、application/javascript*、application/xml*、image/svg+xml*。客户端接受 gzip 或 zstd 时才压缩。 - WebP、PNG、JPEG、GIF 仍不进入这条压缩规则。上游已经带
content-encoding时,Caddy 不会再压一次。 - 证书继续放在
caddy_data。换这份 Compose 并重建 Caddy 容器不会删这个卷,也不需要重新申请已经签发的证书。
这一版不做
- 不改设备码流程、scope、买家注册或支付成功条件。
- 不改店面 HTML 的
no-store,也不改 CSS、JS 的 10 分钟新鲜期和之后最多 24 小时的后台校验。 - 不改公开图片按 id 缓存一年的规则。缺失图片仍是
404。 - 不改一键安装问什么、生成什么,也不默认打开买入。
- 不把 Docker Hub 用户名、令牌、域名或证书写进仓库。
- 不改 Caddy 镜像标签,仍是
caddy:2.11.7-alpine。
升级注意
已经用 v0.2.1 打开 Caddy 的机器,如果 443 没有服务、Caddy 容器反复退出,就是这一处。不要删 .env,也不要删 caddy_data。
一键安装过的机器,等这个正式版发布后执行:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade 会下载这个 tag 的 compose.yaml,保留已有密钥和后来手工打开的买入配置,然后 pull。它不打印已保存的密码。
手工部署的机器不要再跑一键安装。换成这一版的 compose.yaml 后执行:
docker compose up -d不要加 --build。只把 MERCLINK_IMAGE 改成 docker.io/<用户名>/merclink:0.2.2 而不换 Compose,Caddy 仍然起不来。没启用 COMPOSE_PROFILES=caddy 的机器可以不为这个问题单独操作。
回滚时不要只把应用镜像退回 0.2.1 或 0.2.0。0.2.1 的 Compose 会再次让 Caddy 退出。要退回旧代理配置,换回那一版能通过校验的 compose.yaml。latest 会在这个正式版发布时从 0.2.1 移到 0.2.2。
English
Date: 2026-10-14
This is a proxy startup fix for v0.2.1. The store, authorization scopes, public discovery, purchase rules, the rule that only paid means payment success, storefront caching, and the set of compressed types are unchanged. There is no new database migration. Application code is unchanged.
v0.2.1's compose.yaml told Caddy to compress only text, JSON, XML, JavaScript, and SVG. That policy was right, but all five patterns were written on one header Content-Type line. The pinned caddy:2.11.7-alpine rejects that line. The entrypoint exits at caddy run, so public HTTPS never starts. The application container is unaffected. A host that does not enable the caddy profile does not hit this.
This version splits that one match into five lines. The adapted config is still one Content-Type field with five alternatives, not five types that must all match. The production image is docker.io/<namespace>/merclink:0.2.2. The stable release also publishes latest. Pulling only the application image does not fix this. The host must use this version of compose.yaml.
What this version does
- Production Caddy can validate its config and listen on ports 80 and 443. v0.2.1 fails here. The log is
malformed header matcher: expected field. - Compression still matches the response
Content-Type, not the request header. The patterns aretext/*,application/json*,application/javascript*,application/xml*, andimage/svg+xml*. A response is compressed only when the client accepts gzip or zstd. - WebP, PNG, JPEG, and GIF are still not selected. If the upstream response already has
content-encoding, Caddy does not compress it again. - Certificates stay on
caddy_data. Replacing this Compose file and recreating the Caddy container does not delete that volume and does not require reissuing a certificate that is already stored.
What this version does not do
- It does not change the device-code flow, scopes, buyer registration, or the payment-success rule.
- It does not change
no-storeon storefront HTML, or the 10-minute freshness window and later 24-hour background revalidation for CSS and JS. - It does not change the one-year cache for a public image id. A missing image is still
404. - It does not change what one-click install asks for or generates, and it does not turn purchase on by default.
- It does not store a Docker Hub username, token, domain, or certificate in the repository.
- It does not change the Caddy image tag. It remains
caddy:2.11.7-alpine.
Upgrade note
On a host that enabled Caddy with v0.2.1, no listener on port 443 and a Caddy container that keeps exiting is this bug. Do not delete .env or caddy_data.
After this stable release is published, a host installed by the script should run:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade downloads compose.yaml for this tag, keeps generated secrets and any later purchase settings, then pulls. It does not print stored passwords.
Do not run a fresh one-click install over a deployment that was created by hand. Replace compose.yaml with this version, then run:
docker compose up -dDo not add --build. Setting MERCLINK_IMAGE to docker.io/<namespace>/merclink:0.2.2 without replacing Compose leaves Caddy unable to start. A host that does not set COMPOSE_PROFILES=caddy does not need a separate change for this bug.
Do not roll back by moving only the application image to 0.2.1 or 0.2.0. The v0.2.1 Compose file makes Caddy exit again. To restore an older proxy config, also restore a compose.yaml from a version that validates. latest moves from 0.2.1 to 0.2.2 when this stable release is published.
v0.2.1
v0.2.1
日期:2026-10-13
这是 v0.2.0 之后的部署和店面传输修复版。店、授权范围、公开发现、买入规则和支付成功条件没有改。这一版给新机器一条正式版安装入口,并让已发布店面的静态资源和公开图片可以被复用、文本可以被压缩。商品和店铺事实仍每次现算。生产应用镜像是 docker.io/<用户名>/merclink:0.2.1。正式版同时推送 0.2.1 和 latest。
中文
这一版能做什么
- 正式版 GitHub Release 附带
install.sh。永久入口是https://github.com/ptonlix/MercLink/releases/latest/download/install.sh。这个地址跳过预发布,和镜像规则一致:只有正式 tag 才移动latest。 - 新机器执行该脚本后,在
/opt/merclink下载同一 tag 的compose.yaml,生成权限为600的.env,再执行已有的docker compose pull && docker compose up -d。不带--build,不在部署机编译。 - 一键安装把应用镜像写成
docker.io/<用户名>/merclink:latest,不用填写版本号。用户名只在发布时盖进 Release 资源,不进 git,也不写进这份说明。 - 安装只询问公网域名、证书通知邮箱和管理员手机号。已导出的
APEX_DOMAIN才会写入。数据库、Redis、对象存储、管理员和签名密钥在本机生成。管理员初始密码只在安装结束时打印一次。固定打开 Caddy,应用只监听127.0.0.1:3000,APP_BASE_URL是https://加该域名。不写MERCLINK_PURCHASE,买入仍默认关闭。 - 已有
.env时拒绝再次安装,避免覆盖密码。TCP 80 或 443 已被占用时,在写配置前退出,不停掉现有网站。没有 Docker 或 Compose 插件时退出,不安装系统包,也不关闭 TLS 校验。 upgrade只替换镜像和 release tag,并重新下载同一 tag 的compose.yaml,然后 pull。已有密钥,以及后来手工打开的买入配置,都保留。不打印已保存的密码。- 已发布店面里的 CSS、JS 和其他非 HTML 文件允许浏览器缓存 10 分钟,之后最多再把旧文件当作过期响应使用 24 小时,同时在后台用对象键做弱校验。对象键变了才算新文件。重复且未变化的请求返回
304,不再读对象存储。 - 含商品或店铺事实的 HTML 继续
cache-control: no-store。路径不是小写.html但内容类型是 HTML 的文件同样不缓存。 - 店面文本在客户端接受 gzip 且压缩后更小时再压缩。图片和字体保持原字节。公开媒体按图片 id 缓存一年;id 不存在时仍是
404,条件请求不能把缺失图片变成304。 - 正式 Compose 的 Caddy 只压缩文本、JSON、XML、JavaScript 和 SVG,不再压缩 WebP、PNG、JPEG 或 GIF。
这一版不做
- 不改设备码流程、scope、买家注册或支付成功条件。没有新的数据库迁移。
- 不在脚本里安装 Docker、Python 或 openssl,不收集支付宝、短信或验证码密钥,不默认打开买入。
- 不把 Docker Hub 用户名、令牌、域名或生成的密钥写进仓库。
- 不把店面搬到 CDN,不给 CSS 加指纹,不缓存商品查询结果,也不让 HTML 带
max-age。 - 不在应用进程内缓存对象字节。不压缩已经压缩过的图片和字体。
升级注意
已经手工部署的机器不要再跑一键安装。一键安装固定打开 Caddy,并要求 80 和 443 空闲。它也不会接管别的目录里的现有 .env。
这类机器先换成这一版的 compose.yaml,再把 MERCLINK_IMAGE 改为 docker.io/<用户名>/merclink:0.2.1,然后执行:
docker compose pull && docker compose up -d不要加 --build。只改镜像也能让应用自己压缩店面文本并带上缓存头。换成这一版 Compose 后,Caddy 才会按内容类型压缩文本,并跳过商品图。
新机器用:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh域名要先解析到这台机器,并放行 TCP 80 和 443。安装写到一半失败时不要删 .env。应执行 upgrade,或在该目录重新 pull。以后换成更新的正式版:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgrade店主激活新店面后,已经打开过页面的浏览器在 10 分钟内可以继续使用旧 CSS 和 JS,之后最多再使用 24 小时并在后台重新校验。新访客立即看到新文件。价格、库存和店铺资料不从旧 HTML 复用。
回滚应用镜像到 0.2.0 时使用版本号标签,不使用 latest。latest 会被下一个正式版覆盖。只改镜像不会把 Compose 里的 Caddy 压缩规则退回去;要退回旧代理配置,还得换回那一版的 compose.yaml。
English
Date: 2026-10-13
This is a deployment and storefront transfer fix after v0.2.0. The store, authorization scopes, public discovery, purchase rules, and the rule that only paid means payment success are unchanged. This version adds a stable installer for a new machine, and lets released storefront assets and public images be reused while text can be compressed. Product and shop facts are still calculated on each request. The production image is docker.io/<namespace>/merclink:0.2.1. The stable release also publishes latest.
What this version does
- The stable GitHub Release includes
install.sh. The permanent entry ishttps://github.com/ptonlix/MercLink/releases/latest/download/install.sh. That URL skips prereleases, matching the image rule: only a stable tag moveslatest. - On a new machine the script creates
/opt/merclink, downloadscompose.yamlfrom the same tag, writes.envwith mode600, and runs the existingdocker compose pull && docker compose up -d. It does not pass--buildand does not compile on the deploy host. - A one-click install sets the application image to
docker.io/<namespace>/merclink:latest. The operator does not choose a version. The registry username is stamped into the Release asset at publish time. It is not committed and is not written in these notes. - Install asks only for the public hostname, certificate email, and admin phone number.
APEX_DOMAINis written only when the caller has already exported it. Database, Redis, object-storage, admin, and signing secrets are generated on the host. The initial admin password is printed once at the end. Caddy is enabled, the app binds to127.0.0.1:3000, andAPP_BASE_URLishttps://plus that hostname.MERCLINK_PURCHASEis not set, so purchase stays off. - If
.envalready exists, install exits without changing it. If TCP 80 or 443 is already in use, it exits before writing config and does not stop the existing site. If Docker or the Compose plugin is missing, it exits without installing packages and without disabling TLS verification. upgradereplaces only the image and release tag, downloadscompose.yamlfor that same tag, and pulls. Generated secrets and any later purchase settings are kept. Stored passwords are not printed.- Released storefront CSS, JS, and other non-HTML files may be cached for 10 minutes. After that, a browser may reuse the stale file for up to 24 hours while revalidating in the background. The weak validator is the object key, so a new release is a new file. An unchanged repeat request returns
304and does not read object storage again. - HTML that contains product or shop facts stays
cache-control: no-store. A file whose content type is HTML is treated the same way even when the path does not end in lowercase.html. - Released text is gzipped when the client accepts gzip and the compressed body is smaller. Images and fonts stay as stored bytes. Public media is cached for one year by image id. An unknown id is still
404. A conditional request cannot turn a missing image into304. - Production Caddy compresses only text, JSON, XML, JavaScript, and SVG. It does not compress WebP, PNG, JPEG, or GIF.
What this version does not do
- It does not change the device-code flow, scopes, buyer registration, or the payment-success rule. There is no new database migration.
- It does not install Docker, Python, or openssl, collect Alipay, SMS, or captcha secrets, or turn purchase on by default.
- It does not store a Docker Hub username, token, domain, or generated secret in the repository.
- It does not move the storefront to a CDN, fingerprint CSS URLs, cache product queries, or put
max-ageon HTML. - It does not cache object bytes in the application process, and it does not compress images or fonts.
Upgrade note
Do not run a fresh one-click install over a deployment that was created by hand. That installer always enables Caddy and requires ports 80 and 443 to be free. It also does not take over an existing .env in another directory.
On those machines, replace compose.yaml with this version, set MERCLINK_IMAGE to docker.io/<namespace>/merclink:0.2.1, then run:
docker compose pull && docker compose up -dDo not add --build. Changing only the image is enough for the application to gzip storefront text and send the new cache headers. The Caddy content-type rules, including skipping product images, arrive only with this version of Compose.
For a new machine:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.shPoint the domain at the machine first, and open TCP 80 and 443. If install fails after writing config, do not delete .env. Run upgrade, or pull again in that directory. To move an installed host to a later stable release:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeAfter a merchant activates a new storefront, a browser that already opened the shop may keep the old CSS and JS for 10 minutes, then for up to 24 hours while revalidating. A new visitor sees the new files immediately. Price, stock, and profile facts are not reused from old HTML.
Roll the application image back to 0.2.0 by version tag, not by latest. latest moves again on the next stable release. Changing only the image does not remove the Caddy compression rules. To restore the old proxy config, also restore that version of compose.yaml.
v0.2.0
v0.2.0
日期:2026-10-13
这是 v0.1.0 之后的部署修复版。店、授权范围、公开发现和买入规则没有改。这一版换成可匿名拉取的对象存储,补上可选的公网 HTTPS,并让设备码确认留在已配置的公开地址。生产应用镜像是 docker.io/ptonlix/merclink:0.2.0。
中文
这一版能做什么
- 正式环境的对象存储从固定版本 MinIO 换成单盘
rustfs/rustfs:1.0.1。应用仍只通过已有的 S3 变量访问它,不把图片写到本地磁盘。控制台端口9001不发布,图片仍由应用的媒体路由提供。 - 桶由一次性的
amazon/aws-cli:2.27.49创建。没有图片的新部署使用空卷rustfs_data。 - 公网 HTTPS 可以由同一份
compose.yaml里的caddy:2.11.7-alpine提供。只在COMPOSE_PROFILES=caddy时启动。证书由 Caddy 向 Let's Encrypt 申请并续期,保存在caddy_data,不写进仓库。 - 可选
APEX_DOMAIN只 301 到https://APP_DOMAIN,不作为第二个授权或支付地址。 - 设备码确认表单、
verification_uri和授权发现文档的绝对地址改用APP_BASE_URL上的 issuer。容器监听地址0.0.0.0:3000不再出现在这些链接里。 v0.2.0会同时推送0.2.0和latest。回滚仍使用版本号标签,不使用latest。
这一版不做
- 不迁移旧的
minio_data。不把该卷挂给 RustFS,也不提供从 MinIO 拷贝对象的步骤。 - 不把 RustFS 扩成多盘,不发布对象存储控制台。
- 不把域名、证书或 ACME 账号写进仓库。未启用 Caddy 时,仍由部署机自己的反向代理提供 HTTPS。
- 不改设备码流程、scope、买家注册或支付成功条件。没有新的数据库迁移。
升级注意
已有部署先改 .env,再拉镜像。把 MINIO_ROOT_USER 和 MINIO_ROOT_PASSWORD 改名为 RUSTFS_ACCESS_KEY 和 RUSTFS_SECRET_KEY。不要另加一套对象存储变量。APP_BASE_URL 必须是公网地址;启用 Caddy 时必须是 https:// 加 APP_DOMAIN。
需要这份 Compose 提供 HTTPS 时,设置 COMPOSE_PROFILES=caddy、APP_DOMAIN 和 ACME_EMAIL,并把 APP_BIND 设为 127.0.0.1。域名要先解析到这台机器,安全组放行 TCP 80 和 443。不要删除 caddy_data。
然后执行:
docker compose pull && docker compose up -d不要加 --build。旧的 MinIO 图片不会出现在新卷里。需要这些图片时,用管理 Skill 重新上传。已经发出的设备码如果过期,向 Agent 再要一个。
回滚应用镜像到 0.1.0 前,先确认部署仍使用认识的对象存储和 .env 变量名。只改 MERCLINK_IMAGE 不会把 rustfs_data 变回 minio_data。
English
Date: 2026-10-13
This is a deployment fix after v0.1.0. The store, authorization scopes, public discovery, and purchase rules are unchanged. This version switches object storage to an anonymously pullable image, adds optional public HTTPS, and keeps device-code confirmation on the configured public origin. The production image is docker.io/ptonlix/merclink:0.1.1.
What this version does
- Production object storage changes from the pinned MinIO image to single-disk
rustfs/rustfs:1.0.1. The application still reaches it only through the existing S3 variables and does not write images to local disk. Console port9001is not published. Images are still served by the application media route. - The bucket is created once by
amazon/aws-cli:2.27.49. A new deployment with no images uses the emptyrustfs_datavolume. - Public HTTPS can come from
caddy:2.11.7-alpinein the samecompose.yaml. It starts only whenCOMPOSE_PROFILES=caddy. Caddy requests and renews the Let's Encrypt certificate and stores it incaddy_data. The certificate is not committed. - Optional
APEX_DOMAINonly redirects tohttps://APP_DOMAIN. It is not a second authorization or payment origin. - Device confirmation forms,
verification_uri, and authorization discovery URLs now use the issuer onAPP_BASE_URL. The container bind address0.0.0.0:3000no longer appears in those links. v0.2.0also publisheslatest. Roll back by version tag, not bylatest.
What this version does not do
- It does not migrate the old
minio_datavolume, mount that volume on RustFS, or provide a copy procedure from MinIO. - It does not expand RustFS to multiple disks or publish an object-storage console.
- It does not store domains, certificates, or the ACME account in the repository. Without the Caddy profile, HTTPS still comes from the deploy machine's own reverse proxy.
- It does not change the device-code flow, scopes, buyer registration, or the rule that only
paidmeans payment success. There is no new database migration.
Upgrade note
Change .env before pulling. Rename MINIO_ROOT_USER and MINIO_ROOT_PASSWORD to RUSTFS_ACCESS_KEY and RUSTFS_SECRET_KEY. Do not add a second set of object-storage variables. APP_BASE_URL must be the public origin. With Caddy, it must be https:// plus APP_DOMAIN.
To let this Compose file provide HTTPS, set COMPOSE_PROFILES=caddy, APP_DOMAIN, and ACME_EMAIL, and set APP_BIND to 127.0.0.1. Point the domain at the machine first, and open TCP 80 and 443. Do not delete caddy_data.
Then run:
docker compose pull && docker compose up -dDo not add --build. Pictures stored in the old MinIO volume do not appear in the new volume. Upload them again through the management Skill if they are still needed. If an already issued device code has expired, ask the agent for a new one.
Before rolling the application image back to 0.1.0, confirm the deployment still uses object storage and .env names that version understands. Changing only MERCLINK_IMAGE does not turn rustfs_data back into minio_data.
v0.1.0
v0.1.0
日期:2026-10-13
这是 MercLink 的第一个可部署版本。它把 2026-10-05 到 2026-10-13 的提交收成一家店、一个进程、一套公开发现和一套可选买入。生产应用镜像是 docker.io/ptonlix/merclink:0.1.0。
中文
这一版能做什么
- 启动时创建唯一店主和这一家店。店主就是管理员,授权页不能注册商家。
- 商家 Agent 通过设备码授权,用管理 Skill 建目录、定义字段、创建带规格的商品,并上架或下架。不收集密码、短信验证码或长期 API Key。
- 公开页、
/llms.txt和用户 Skill 使用同一套已上架商品事实。公开价格以主要单位和 Markdown 展示,不接受商家自写的价格文本。 - 商品页展示字段标签,机器标识留在商品 Markdown 里。接口目录和字段表示例从同一张字段表生成,避免另写一套协议。
- 店面可以由 Agent 替换。生成来自当前公开页,不执行上传的授权 HTML,也不能用预览请求改掉发布本身或把请求转到别的主机。
- 可选的执照和联系事实可以标识这家店,不填也不阻止发布。
- 商品图片只进已配置的 S3 或 MinIO。短信和上传次数由 Redis 限制。Redis 或对象桶不可用时拒绝对应操作,不降级到本地磁盘。
- 买入默认关闭。未设置
MERCLINK_PURCHASE=1时,部署不要求验证码、短信和支付密钥,用户 Skill 只教查询。 - 开启买入后,买家首次授权用手机号注册。买家 Agent 必须留着令牌,才能继续查自己的订单。下单仍是一行。支付可以走支付宝电脑收银台、支付宝手机网站,或启动时配置的易支付。只有订单状态
paid表示支付成功。迟到的支付不会把已关闭订单改成已支付。 - 正式部署拉取已发布镜像,不再在部署机编译应用。PostgreSQL 18.6、Redis 8.10.2 和固定版本 MinIO 仍由
compose.yaml拉起。v0.1.0会同时推送0.1.0和latest。回滚使用版本号标签,不使用latest。
这一版不做
- 不做购物车、优惠券、运费、推荐、退款、商家自助注册,也不接其他电商平台。
- 不做多店、主题市场和人点的商品后台。管理页只改店主密码。
- 不在 Agent 页面收集支付宝密码,也不把同步回跳当成支付成功。
升级注意
已有部署要保持下单,升级前必须设置 MERCLINK_PURCHASE=1。数据库迁移只向前追加。回滚应用镜像前,先确认该版本认识已经执行过的表结构。
English
Date: 2026-10-13
This is the first deployable MercLink release. It collects the work from 2026-10-05 through 2026-10-13 into one store, one process, one public discovery surface, and an optional purchase flow. The production image is docker.io/ptonlix/merclink:0.1.0.
What this version does
- Startup creates the only store owner and the only store. The owner is the administrator. The authorization page cannot register a merchant.
- A merchant agent authorizes with a device code, then uses the management Skill to create catalogs, fields, specified products, and to publish or unpublish them. The product does not collect passwords, SMS codes, or long-lived API keys for the agent.
- Public pages,
/llms.txt, and the user Skill share the same published-product facts. Public prices use major units and Markdown. A merchant cannot supply a separate price string. - Product pages show field labels. Machine identifiers stay in the product Markdown. The interface directory and field examples are generated from the same field table, so they do not become a second protocol.
- An agent can replace the storefront. Generation comes from the live public pages. Uploaded authorization HTML is not executed, and a preview request cannot reset the release or choose another host.
- Optional license and contact facts can identify the store. Leaving them empty does not block publishing.
- Product images go only to the configured S3 or MinIO bucket. SMS and upload counts are limited in Redis. If Redis or the bucket is unavailable, that operation is refused. There is no local-disk fallback.
- Purchase is off by default. Without
MERCLINK_PURCHASE=1, the deployment does not require captcha, SMS, or payment credentials, and the user Skill only teaches lookup. - With purchase enabled, the buyer registers by phone number on first authorization. The buyer agent must retain its token to look up its own orders later. An order is still one line. Payment can use the Alipay desktop cashier, Alipay mobile website pay, or the EasyPay provider selected at startup. Only the order status
paidmeans payment succeeded. A late payment does not mark a closed order paid. - Production pulls the published image instead of compiling on the deploy machine. PostgreSQL 18.6, Redis 8.10.2, and the pinned MinIO image still come from
compose.yaml.v0.1.0also publisheslatest. Roll back by version tag, not bylatest.
What this version does not do
- No cart, coupons, shipping fees, recommendations, refunds, merchant self-registration, or other commerce platforms.
- No multi-store setup, theme marketplace, or human product admin. The admin page only changes the owner password.
- Agent pages do not collect an Alipay password, and a synchronous return URL is not payment success.
Upgrade note
An existing deployment that must keep taking orders has to set MERCLINK_PURCHASE=1 before upgrading. Database migrations only move forward. Before rolling an application image back, confirm that version understands the schema already applied.