Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# CI/CD: every push to main runs the parser tests and then deploys the
# Worker (portal page + API) to Cloudflare via wrangler — no Cloudflare
# git integration involved.
#
# Required repo secrets:
# CLOUDFLARE_API_TOKEN Cloudflare API token, permission "Workers Scripts: Edit"
# (add "Workers Custom Domains: Edit" + zone DNS: Edit for
# the dev.cardputer.cc custom domain on first deploy)
# CLOUDFLARE_ACCOUNT_ID Cloudflare account id (dashboard right sidebar)
#
# Optional repo secrets (when present they are synced to the Worker after
# each deploy, so you never need to run `wrangler secret put` locally):
# OAUTH_CLIENT_ID GitHub OAuth App client id -> GITHUB_CLIENT_ID
# OAUTH_CLIENT_SECRET GitHub OAuth App client secret -> GITHUB_CLIENT_SECRET
# BOT_TOKEN fine-grained PAT, contents:write on CardputerZero/packages
# SESSION_SECRET e.g. `openssl rand -hex 32`
# (GitHub forbids secret names starting with GITHUB_, hence the OAUTH_* aliases.)
name: Deploy to Cloudflare

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

concurrency:
group: deploy-worker
cancel-in-progress: false

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install parser test dependencies
run: sudo apt-get update && sudo apt-get install -y dpkg-dev xz-utils zstd
- name: Run deb parser tests
run: node --test test/*.test.js

deploy:
# PRs only run tests; the actual deploy happens on push to main (or manual dispatch).
if: github.event_name != 'pull_request'
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Deploy Worker
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: worker

- name: Sync Worker secrets (only those configured in GitHub)
working-directory: worker
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
SYNC_GITHUB_CLIENT_ID: ${{ secrets.OAUTH_CLIENT_ID }}
SYNC_GITHUB_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }}
SYNC_BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
SYNC_SESSION_SECRET: ${{ secrets.SESSION_SECRET }}
run: |
set -euo pipefail
for name in GITHUB_CLIENT_ID GITHUB_CLIENT_SECRET BOT_TOKEN SESSION_SECRET; do
val="$(printenv "SYNC_$name" || true)"
if [ -n "$val" ]; then
printf '%s' "$val" | npx --yes wrangler secret put "$name"
echo "synced secret: $name"
else
echo "skipped secret (not set in GitHub): $name"
fi
done
94 changes: 93 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,93 @@
# dev-portal
# CardputerZero 开发者中心(dev.cardputer.cc)

面向开发者的 AppStore 网页上传入口。开发者在浏览器里直接上传 `.deb`,
页面**本地解析**出应用图标、包名、版本号、`.desktop` 声明、Maintainer 邮箱,
生成初步安全报告;GitHub 登录校验邮箱真实性,包名**抢占式归属**(先提交者
拥有该包名,之后只有同邮箱账号或管理员可以更新/下架);自动化审核通过后
自动生成发布 PR(可配置自动合并),APT 索引与商店 registry 随之更新。

> 本仓库是站点源码(Cloudflare Worker 架构,无数据库)。归属与版本状态
> 直接以线上 APT 索引为准,天然免维护。

## 工作原理

```
浏览器 dev.cardputer.cc
│ ① 选择 .deb → 本地解析(ar/tar/gz/xz/zstd 纯前端解包)
│ 展示图标 / 包名 / 版本 / .desktop / 邮箱 / 文件清单 / 安全报告
│ 并对照线上索引预检:包名归属、版本是否重复/倒退
│ ② GitHub OAuth 登录(scope: user:email,读取已验证邮箱)
│ ③ POST /api/submit(同域,无 CORS)
Cloudflare Worker(本仓库 worker/,静态页面同域托管)
│ ④ 会话校验 + 归属/版本预检(拉线上 Packages 索引比对)
│ ⑤ .deb 存入 packages 仓库 web-upload-buffer Release(BOT_TOKEN)
│ ⑥ repository_dispatch: web-submission
CardputerZero/packages Actions(packages-workflows/ 里的两个 workflow)
│ ⑦ dpkg-deb 权威校验:完整性 / control / .desktop / setuid / 设备文件 /
│ 越权路径 / maintainer 脚本危险模式 / 邮箱归属 / 版本单调
│ ⑧ store 元数据:优先源码仓库 app-builder.json,否则从 deb 自动生成
│ ⑨ 通过 → 发布 PR(AUTO_MERGE=true 时自动合并);失败 → issue @提交者
update-index.yml(已有)→ .deb 提升进 apt-pool → APT 索引/商店 JSON 更新
```

信任模型:浏览器解析只做**预览与提前拦截**(好体验);一切以 Actions 里
`dpkg-deb` 的服务端校验为准,客户端传来的任何字段都不会被直接采信。

## 抢占式包名归属

- 新包名:任何人首次提交即占有(deb 的 `Maintainer` 邮箱必须是提交者
GitHub 账号的已验证邮箱或 `<login>@users.noreply.github.com`)。
- 已有包名:仅当线上包的 Maintainer 邮箱 ∈ 提交者已验证邮箱时可更新/下架。
- 管理员:`wrangler.toml` 的 `ADMIN_LOGINS`(逗号分隔的 GitHub 登录名)
可管理任意包。
- 相同版本再次提交 → 前端与服务端都会提示"版本已存在,请提升版本号"。

## 初步安全报告(浏览器 + CI 双层)

- setuid/setgid、全局可写、设备文件、路径穿越
- 安装路径白名单(`usr/share/APPLaunch/`、`lib/systemd/system/`、
`usr/share/<pkg>/`、`usr/lib/<pkg>/`、`opt/<pkg>/`、`usr/share/doc/`)
- ELF 架构核对(非 arm64 告警)
- maintainer 脚本危险模式(`rm -rf /`、`curl|sh`、写设备、动 passwd/cron 等)
- 缺 `.desktop`/图标、包名/架构不合法、超大文件

## 部署(GitHub Actions CI/CD)

**主路径就是 CI/CD**:push `main` 即自动测试 + `wrangler deploy` 到
Cloudflare Workers(不使用 Cloudflare 的 git 集成 / 自动拉取部署)。
完整步骤(域名接入、OAuth App、bot token、GitHub secrets 配置)见
[docs/DEPLOY.md](docs/DEPLOY.md)。

概要:

1. `cardputer.cc` 的 zone 已在 Cloudflare(NS 已切);`wrangler.toml` 声明的
`dev.cardputer.cc` 自定义域会在首次部署时自动接管 DNS + 证书。
2. 仓库 Settings → Secrets and variables → Actions 配置:
- `CLOUDFLARE_API_TOKEN`(必需,Workers Scripts: Edit;首次部署自定义域
还需 Workers Custom Domains: Edit + zone DNS: Edit)
- `CLOUDFLARE_ACCOUNT_ID`(必需)
- `OAUTH_CLIENT_ID` / `OAUTH_CLIENT_SECRET` / `BOT_TOKEN` /
`SESSION_SECRET`(可选:配置后每次部署自动同步到 Worker secrets;
也可以本地 `wrangler secret put` 一次性设置)
3. push `main`(或手动 workflow_dispatch)→ 自动部署。
4. `packages-workflows/` 下的两个文件复制到 `CardputerZero/packages` 的
`.github/workflows/`(一次性)。想保留人工审核就把其中 `AUTO_MERGE`
改为 `"false"`(PR 仍会自动创建,由管理员合并)。

在 `worker/wrangler.toml` 的 `[vars] ADMIN_LOGINS` 里填管理员的 GitHub
登录名(逗号分隔)。

## 本地开发

```bash
cd worker && wrangler dev # http://localhost:8787,页面 + API 同域
```

解析器单元测试(用系统 gzip/xz/zstd 模拟浏览器解压):

```bash
node --test test/*.test.js
```
121 changes: 121 additions & 0 deletions docs/DEPLOY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# 部署指南(Cloudflare Worker + GitHub Actions CI/CD)

本仓库通过 **GitHub Actions** 部署到 Cloudflare Workers(`wrangler deploy`),
**不是** Cloudflare 控制台的 "连接 GitHub 自动拉取" 集成。push `main`
分支即自动测试并部署,全程可在仓库 Actions 页追踪。

```
push main ──▶ GitHub Actions(.github/workflows/deploy.yml)
│ ① node --test:deb 解析器单元测试
│ ② wrangler deploy(cloudflare/wrangler-action)
│ ③ 可选:把 GitHub secrets 同步到 Worker secrets
Cloudflare Workers(dev.cardputer.cc,静态页面 + API 同域)
```

## 一、域名接入 Cloudflare(一次性)

1. Cloudflare 控制台 → Add site → `cardputer.cc`(免费版即可),把域名 NS
切到 Cloudflare(当前已完成:`eva.ns.cloudflare.com` / `nick.ns.cloudflare.com`)。
2. 主站 `cardputer.cc` 继续指向 GitHub Pages(A 记录 185.199.108–111.153),
互不影响;开发者中心用 `dev.cardputer.cc` 子域,由 Worker 的
custom domain 自动接管 DNS + 证书,**无需手动加 DNS 记录**。

`worker/wrangler.toml` 中已声明:

```toml
routes = [
{ pattern = "dev.cardputer.cc", custom_domain = true },
]
```

## 二、创建 GitHub OAuth App(一次性)

GitHub → Settings → Developer settings → OAuth Apps → New OAuth App:

- Homepage URL: `https://dev.cardputer.cc`
- Authorization callback URL: `https://dev.cardputer.cc/auth/callback`

记下 Client ID / Client Secret(下面配 secrets 用)。

## 三、准备 bot token(一次性)

建议用机器人账号(或组织管理员)创建 fine-grained PAT:

- Repository access: 仅 `CardputerZero/packages`
- Permissions: **Contents: Read and write**(上传 buffer Release 资产 +
触发 `repository_dispatch`)

## 四、配置 GitHub Actions secrets

本仓库 Settings → Secrets and variables → Actions → New repository secret:

| Secret | 必需 | 说明 |
|--------|------|------|
| `CLOUDFLARE_API_TOKEN` | ✅ | Cloudflare API token。My Profile → API Tokens → Create Token,权限 **Workers Scripts: Edit**;首次部署要建自定义域,还需 **Workers Custom Domains: Edit** 和 `cardputer.cc` zone 的 **DNS: Edit** |
| `CLOUDFLARE_ACCOUNT_ID` | ✅ | Cloudflare 控制台任一域名概览页右侧栏的 Account ID |
| `OAUTH_CLIENT_ID` | 可选 | 第二步的 Client ID → 同步为 Worker secret `GITHUB_CLIENT_ID` |
| `OAUTH_CLIENT_SECRET` | 可选 | 第二步的 Client Secret → 同步为 `GITHUB_CLIENT_SECRET` |
| `BOT_TOKEN` | 可选 | 第三步的 PAT → 同步为 `BOT_TOKEN` |
| `SESSION_SECRET` | 可选 | `openssl rand -hex 32` → 同步为 `SESSION_SECRET` |

> GitHub 不允许 secret 名以 `GITHUB_` 开头,所以 OAuth 两项在 GitHub 侧叫
> `OAUTH_*`,workflow 会以 Worker 侧的正式名字 `GITHUB_CLIENT_ID` /
> `GITHUB_CLIENT_SECRET` 写入。
>
> 4 个"可选"项:配置在 GitHub 后**每次部署自动同步**到 Worker(推荐,改
> 密钥只需改 GitHub secret 再跑一次部署);不配置则 workflow 跳过同步,
> 需要本地手动设置一次:
>
> ```bash
> cd worker
> wrangler login
> wrangler secret put GITHUB_CLIENT_ID
> wrangler secret put GITHUB_CLIENT_SECRET
> wrangler secret put BOT_TOKEN
> wrangler secret put SESSION_SECRET
> ```

## 五、触发部署

- push 任何提交到 `main` → 自动测试 + 部署;
- 或 Actions → "Deploy to Cloudflare" → Run workflow 手动触发。

部署完成后访问 `https://dev.cardputer.cc` 验证。

## 六、packages 仓库侧(一次性)

把 `packages-workflows/` 下的两个 workflow 复制到
`CardputerZero/packages` 的 `.github/workflows/`:

- `process-web-submission.yml` — 接收网页提交,dpkg-deb 权威校验,
通过后自动开发布 PR(`AUTO_MERGE: "true"` 时自动合并;改为
`"false"` 则保留人工审核);失败开 issue @提交者。
- `process-web-unpublish.yml` — 接收下架请求,生成移除 PR。

并在 `packages` 仓库建 `web-submission-failed` label(失败反馈 issue 用)。

## 安全设计

- **身份**:GitHub OAuth(scope `user:email`,读已验证邮箱);Worker 用
HMAC 签名的 HttpOnly cookie 维持 24h 会话,不保存用户 OAuth token。
- **信任边界**:用户上传内容一律视为不可信——浏览器解析只做预览体验,
Worker 只做魔数/大小/归属/版本预检;语义校验全部在 packages 仓库
Actions 里用 `dpkg-deb` 完成,`.deb` 永不被执行。
- **身份绑定**:包的 `Maintainer` 邮箱必须 ∈ 提交者的 GitHub 已验证邮箱
(或 `<login>@users.noreply.github.com`),抢占式归属,防冒名顶替或
覆盖他人的包;`ADMIN_LOGINS` 管理员可管理任意包。
- **payload 传递**:workflow 中所有来自 dispatch payload 的值只经 env
传入 shell,杜绝模板注入。
- **上限**:Worker 默认 64 MB(`MAX_SIZE_MB`),Cloudflare 免费版请求体
上限 100 MB。
- 可选加固:Cloudflare 侧对 `/api/submit` 配 rate limiting 规则防刷。

## 与 OSS 直传的取舍

如果更希望文件落在阿里云 OSS(国内直传快):OSS bucket 支持配置 CORS 允许
浏览器直传,但生成上传签名同样需要一个服务端(函数计算/STS),之后还要一跳
回调 GitHub API。整体链路比 Worker 方案多一层,且 packages 管线最终仍要从
URL 拉取 `.deb` 校验。因此推荐当前方案(`.deb` 直接进 GitHub Release,与
现有 apt-pool 分发/OSS 镜像同步逻辑无缝衔接);将来如需国内上传加速,只需
把 Worker 的转存一步换成"签名直传 OSS + manifest url 指向 OSS",其余不变。
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "cardputerzero-dev-portal",
"private": true,
"type": "module",
"scripts": {
"test": "node --test test/*.test.js",
"dev": "wrangler dev --cwd worker",
"deploy": "wrangler deploy --cwd worker"
}
}
Loading
Loading