apkgo

May 8, 2026 · View on GitHub

GitHub release build go license skills.sh

🌐 Language / 语言: English · 简体中文

一行命令,将 APK 发布到所有主流安卓应用商店。为 CI/CD 和 AI Agent 设计。

不想搭 CI、不想碰命令行? 试试托管版 apkgo cloud —— 浏览器打开就能发版,凭证云端托管、多人协作、发布历史可追溯,免装免运维,运营和产品同事也能独立上手。

不止安卓? 白辞 baici.tech 提供 iOS / 鸿蒙 / 微信、支付宝、抖音小程序的一站式上架代办,覆盖 ICP 备案、软件著作权、应用核准全流程。98% 一次过审、24h 极速响应、不过退款,已服务 500+ 开发者与企业。

安装

macOS / Linux — 一行脚本自动识别 OS/架构、下载、校验 SHA-256:

curl -fsSL https://apkgo.com.cn/install.sh | sh

默认装到 /usr/local/bin,不可写时会提示用 sudoAPKGO_INSTALL_DIR=$HOME/.local/bin sh。 锁版本:APKGO_VERSION=v3.1.0 sh

其他方式:

# AI Agent Skill (支持 Claude Code、Cursor、Windsurf 等 40+ agent)
npx skills add KevinGong2013/apkgo

# Go
go install github.com/KevinGong2013/apkgo@latest

# Docker
docker pull ghcr.io/kevingong2013/apkgo:latest
手动下载 / Windows
# macOS (Apple Silicon)
curl -fsSL https://github.com/KevinGong2013/apkgo/releases/latest/download/apkgo_Darwin_arm64.tar.gz | tar xz -C /usr/local/bin apkgo

# macOS (Intel)
curl -fsSL https://github.com/KevinGong2013/apkgo/releases/latest/download/apkgo_Darwin_x86_64.tar.gz | tar xz -C /usr/local/bin apkgo

# Linux (x86_64)
curl -fsSL https://github.com/KevinGong2013/apkgo/releases/latest/download/apkgo_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin apkgo

# Linux (arm64)
curl -fsSL https://github.com/KevinGong2013/apkgo/releases/latest/download/apkgo_Linux_arm64.tar.gz | tar xz -C /usr/local/bin apkgo

# Windows (PowerShell)
# 从 https://github.com/KevinGong2013/apkgo/releases/latest 下载 apkgo_Windows_x86_64.zip
# 解压后将 apkgo.exe 添加到 PATH

快速开始

# 1. 生成配置文件
apkgo init

# 2. 填写商店凭证
vim apkgo.yaml

# 3. 上传
apkgo upload -f app.apk

用法

上传

# 上传到所有配置的商店
apkgo upload -f app.apk

# 上传到指定商店
apkgo upload -f app.apk --store huawei,xiaomi

# 带更新日志
apkgo upload -f app.apk --notes "修复了登录问题"
apkgo upload -f app.apk --notes-file CHANGELOG.md

# 分架构包
apkgo upload -f app-arm32.apk --file64 app-arm64.apk

# 只验证不上传
apkgo upload -f app.apk --dry-run

初始化配置

# 生成包含所有商店的配置
apkgo init

# 只生成指定商店的配置
apkgo init --store huawei,xiaomi

# 指定配置文件路径
apkgo init -c production.yaml

查看支持的商店

apkgo stores

体检(验证商店配置)

doctor 在不上传文件的前提下,校验已配置商店的凭证和权限是否到位,避免真实上传到一半才发现问题:

# 校验所有已配置商店的凭证
apkgo doctor

# 只校验指定商店
apkgo doctor -s huawei

# 提供包名后会跑更深入的检查(包名映射、发布权限等)
apkgo doctor -s huawei -p com.example.app
apkgo doctor -s huawei -f app.apk         # 从 APK 自动取包名

任一探针失败时,退出码为 1。目前 Huawei 已支持,其他商店标记为 doctor not implemented

配置文件

apkgo.yaml:

# hooks 为可选配置,不需要可以不写
hooks:
  before: "./scripts/validate.sh"          # 所有上传前执行
  after: "./scripts/notify.sh"             # 所有上传后执行

stores:
  huawei:
    # 推荐:服务账号(PS256 JWT)
    service_account_file: "/secure/path/huawei-sa.json"
    # 或者: service_account: "<base64(JSON)>"
    # app_id: ""  # 可选,不填则自动通过包名查询
    before: "./scripts/before-huawei.sh"   # 可选,该商店上传前执行
    after: "./scripts/after-huawei.sh"     # 可选,该商店上传后执行

  xiaomi:
    email: "your@email.com"
    private_key: "your-private-key"             # 小米后台的「接口密钥」(被 SDK 当作 password 使用)
    cert_file: "/secure/path/xiaomi-pubkey.cer" # 公钥证书(也支持 cert: <PEM 内容> 或 cert: <base64>)

  oppo:
    client_id: "your-client-id"        # 19 位数字
    client_secret: "your-client-secret"

  vivo:
    access_key: "your-access-key"
    access_secret: "your-access-secret"

  honor:
    client_id: "your-client-id"
    client_secret: "your-client-secret"
    app_id: "your-app-id"

  tencent:
    user_id: "your-user-id"
    access_secret: "your-access-secret"
    app_id: "your-app-id"
    # 多 app: app_id_map: '{"com.foo":"111","com.bar":"222"}'

  pgyer:
    api_key: "your-pgyer-api-key"

  fir:
    api_token: "your-fir-api-token"

  # 单个脚本
  script:
    command: "./deploy.sh"

  # 多个脚本实例 (script.实例名)
  script.cdn-upload:
    command: "./upload-cdn.sh"
  script.dingtalk:
    command: "./notify-dingtalk.sh"

Hooks 说明

Hooks 是可选功能,不配置则不生效。Hook 脚本通过 stdin 接收 JSON 上下文,通过退出码控制流程:

  • before hook 失败(非零退出码)→ 中止上传
  • after hook 失败 → 仅记录警告,不影响结果
  • 自动注入环境变量:APKGO_STOREAPKGO_PACKAGEAPKGO_VERSION
  • stderr 输出作为错误信息

全局 before hook (hooks.before) stdin:

{
  "file_path": "/path/to/app.apk",
  "apk": {"package": "com.example.app", "version_name": "1.0.0", "version_code": 1, "app_name": "MyApp"},
  "stores": ["huawei", "xiaomi"]
}

全局 after hook (hooks.after) stdin:

{
  "file_path": "/path/to/app.apk",
  "apk": {"package": "com.example.app", "version_name": "1.0.0", "version_code": 1, "app_name": "MyApp"},
  "results": [
    {"store": "huawei", "success": true, "duration_ms": 12300},
    {"store": "xiaomi", "success": false, "error": "auth failed", "duration_ms": 400}
  ]
}

商店 before hook (stores.<name>.before) stdin:

{
  "file_path": "/path/to/app.apk",
  "apk": {"package": "com.example.app", "version_name": "1.0.0", "version_code": 1, "app_name": "MyApp"},
  "store": "huawei"
}

商店 after hook (stores.<name>.after) stdin:

{
  "file_path": "/path/to/app.apk",
  "apk": {"package": "com.example.app", "version_name": "1.0.0", "version_code": 1, "app_name": "MyApp"},
  "store": "huawei",
  "result": {"store": "huawei", "success": true, "duration_ms": 12300}
}

Hook 脚本示例(上传完成后发送钉钉通知):

#!/bin/bash
input=$(cat)
results=$(echo "$input" | jq -r '.results[] | "\(.store): \(if .success then "✓" else "✗ "+.error end)"')
curl -s -X POST "$DINGTALK_WEBHOOK" \
  -H "Content-Type: application/json" \
  -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"APK 发布完成 v${APKGO_VERSION}\n${results}\"}}"

环境变量

CI/CD 环境中可通过环境变量配置凭证,无需配置文件:

# 格式: APKGO_<商店名>_<字段名>=值
export APKGO_HUAWEI_SERVICE_ACCOUNT="$(base64 -w0 huawei-sa.json)"  # 推荐
export APKGO_XIAOMI_EMAIL="your@email.com"
export APKGO_XIAOMI_PRIVATE_KEY="your-接口密钥"
export APKGO_XIAOMI_CERT="$(base64 -w0 xiaomi-pubkey.cer)"
export APKGO_OPPO_CLIENT_ID="your-19-digit-id"
export APKGO_OPPO_CLIENT_SECRET="your-secret"
export APKGO_VIVO_ACCESS_KEY="your-key"
export APKGO_VIVO_ACCESS_SECRET="your-secret"
export APKGO_TENCENT_USER_ID="your-user-id"
export APKGO_TENCENT_ACCESS_SECRET="your-secret"
export APKGO_TENCENT_APP_ID="your-app-id"
# 多 app: APKGO_TENCENT_APP_ID_MAP='{"com.foo":"111","com.bar":"222"}'
export APKGO_PGYER_API_KEY="your-pgyer-key"
export APKGO_FIR_API_TOKEN="your-fir-token"

# 环境变量会覆盖配置文件中的同名字段
# 如果没有配置文件,完全通过环境变量配置也可以
apkgo upload -f app.apk --store huawei

凭证不落盘(cloud worker / CI / 多租户)

--creds-from 让 apkgo 从非磁盘源读取 JSON 格式的凭证。orchestrator 把凭证从 secrets manager(Vault / AWS SM / GCP SM 等)取出后通过 stdin 或文件描述符直接注入子进程,全程不写盘、不进 env

# 方式 A: stdin
vault read -format=json secret/apkgo | jq .data \
  | apkgo upload -f app.apk --creds-from=stdin

# 方式 B: 文件描述符(适合 stdin 已经被占用的场景)
apkgo upload -f app.apk --creds-from=fd:3 3<<<"$(vault-creds-as-json)"

JSON 格式跟 yaml 一一对应:

{
  "stores": {
    "huawei": {"service_account": "<base64>"},
    "tencent": {
      "user_id": "...",
      "access_secret": "...",
      "app_id_map": "{\"com.foo\":\"111\"}"
    }
  },
  "hooks": {"before": "...", "after": "..."}
}

apkgo 解析完立刻把输入字节 zero-out,避免 secret 留在缓冲区。设了 --creds-from--configAPKGO_* env 都被忽略。

凭证获取指南

商店控制台地址说明
华为AppGallery Connect用户与权限 > 服务账号(详细步骤
小米小米开放平台账号管理 > 接口密钥(详细步骤
OPPOOPPO 开放平台管理中心 > API 密钥管理(详细步骤
vivovivo 开放平台账号管理 > API 接入(详细步骤
荣耀荣耀开发者平台API 管理(详细步骤
腾讯腾讯开放平台应用 > 账户管理 > API 发布接口 > 申请开通(详细步骤
蒲公英pgyer.com账户设置 > API 密钥(详细步骤
fir.imbetaqr.com.cn账户 > API Token(详细步骤

每家的凭证申请流程都以官方文档为准(链接见下文),README 这边只描述 apkgo 特有的事:要哪几个字段、doctor 怎么验、需要注意的非显然行为。

华为 AppGallery Connect

📖 官方文档:Service Account 接入介绍

推荐用开发者级服务账号(PS256 JWT 鉴权),不要选项目级——访问发布 API 会被拒。下载到的 JSON 凭证文件直接交给 apkgo:

stores:
  huawei:
    service_account_file: "/secure/path/huawei-sa.json"
    # 或 base64(JSON) 内联:service_account: "ewogICJrZXlfaWQiOiAi..."

旧版 client_id + client_secret 仍兼容,但华为已不推荐。

apkgo doctor -s huawei -p com.example.app

3 项探针:token / appid-list(包名 → appId)/ release-permission(应用发布权限)。

小米开放平台

📖 官方文档:API 上传应用

要在小米后台「接口密钥」页面拿两样东西:接口密钥(SDK 里叫 password)和公钥证书.cer 文件)。两个都是开发者账号绑定的。

stores:
  xiaomi:
    email: "<开发者账号邮箱>"
    private_key: "<接口密钥>"
    cert_file: "/secure/path/xiaomi-pubkey.cer"
    # 也支持: cert: "-----BEGIN CERTIFICATE-----..." 或 base64(.cer)
apkgo doctor -s xiaomi -p com.example.app

⚠️ apkgo v3.0 之前内置了一份公钥证书,但那份 2023-05 已过期(且来源不明),从 v3.0 起必须自己提供。

OPPO 开放平台

📖 官方文档:发布接口接入指引

stores:
  oppo:
    client_id: "<19 位数字>"
    client_secret: "<密钥>"
apkgo doctor -s oppo -p com.example.app

OPPO 的发布是异步任务,apkgo 会自动处理两个非显然的状态:撞 911216 任务处理中 时跳过 publish 直接等任务结束;撞 911215 应用审核中 视为成功(已进入审核队列)。

vivo 开放平台

📖 官方文档:开放接口指引

stores:
  vivo:
    access_key: "<...>"
    access_secret: "<...>"
apkgo doctor -s vivo -p com.example.app

vivo 的错误码分两层:网关 code + 业务 subCode。apkgo 同时识别两层,错误信息直接打印中文消息(比如 [15042] 请上传与历史签名一致的APK包...)。

荣耀开发者平台

📖 官方文档:发布接口指南

stores:
  honor:
    client_id: "<...>"
    client_secret: "<...>"
    # app_id: ""  # 可选,不填则按 APK 包名自动查
apkgo doctor -s honor -p com.example.app

doctor app-detail 探针会预检 应用简介(intro)—— 这个字段在荣耀后台必须填,否则 update-language-info 会以 [20076] app introduction is empty 拒绝。先在控制台填好再发版。

腾讯应用宝

📖 官方文档:API 接口传包-接入介绍

腾讯没有 list 或 pkg→id 反查接口,所以 app_id 必须手填。一份 yaml 服务多个应用用 app_id_map

stores:
  tencent:
    user_id: "<开发者 ID>"
    access_secret: "<接口密钥>"
    # 单 app:
    app_id: "<应用 ID>"
    # 多 app: 按 APK 包名命中
    # app_id_map: '{"com.example.foo":"111","com.example.bar":"222"}'
apkgo doctor -s tencent -p com.example.app

发布是异步任务,apkgo 会轮询 query_app_update_status 直到 audit_status 终态(最长 5 分钟);超时视为成功(任务已交给腾讯)。

蒲公英 (Pgyer)

📖 官方文档:API 上传应用

stores:
  pgyer:
    api_key: "<...>"
apkgo doctor -s pgyer -p com.example.app

fir.im

📖 官方文档:betaqr.com.cn/docs

stores:
  fir:
    api_token: "<...>"
apkgo doctor -s fir

⚠️ fir 上传要求账号已完成实名认证,否则 /apps 接口会以 没有实名认证不能上传app 拒绝。先去后台做实名再用。

AI Agent 集成

apkgo 的输出格式专为 AI Agent 和自动化场景设计:

结构化 JSON 输出 (stdout):

{
  "apk": {"package": "com.example.app", "version_name": "1.0.0", "version_code": 1},
  "results": [
    {"store": "huawei", "success": true, "category": "success", "duration_ms": 12300},
    {"store": "oppo",   "success": true, "category": "already_done", "duration_ms": 3200},
    {"store": "xiaomi", "success": false, "category": "policy_block", "error": "签名不一致...", "duration_ms": 400}
  ]
}

Category(重试决策提示):每个 result 带一个 category 字段,把各家千奇百怪的错误码归成 cloud orchestrator 友好的几个桶,避免父进程解析中文错误。可能的值:

Category含义建议处理
success上传成功mark done
already_done该版本已在商店侧(如 OPPO 911215 应用审核中)mark done,不重试
auth_failed凭证错让用户改 secret,不重试
network_retry网络超时 / 5xx退避后重试
store_busy商店限流 / 上次任务还没完等几分钟再重试
policy_block签名不一致、审核驳回等业务规则拒绝让用户处理,不重试
config_invalid后台元数据缺失(intro / 分类 / publisher entity)让用户去后台填,不重试
unknown还没分类到默认按"不可重试"处理

语义化退出码:

Code含义
0全部成功
1部分失败
2全部失败
3输入错误

可发现的配置 schema:

apkgo stores  # 返回每个商店需要的配置字段

非交互: 无 prompt、无确认,适合无人值守环境。

实时进度流(NDJSON):父进程 fork apkgo 想实时拿进度时,加 --progress-stream,stdout 变成每行一个 JSON 事件:

apkgo upload -f app.apk --progress-stream
{"type":"start","apk":{"package":"...","version_name":"1.2.0","version_code":120},"stores":["huawei","xiaomi"]}
{"type":"phase","store":"huawei","phase":"auth"}
{"type":"phase","store":"huawei","phase":"uploading"}
{"type":"total","store":"huawei","total_bytes":62914560}
{"type":"bytes","store":"huawei","sent":7045120,"total":62914560}
{"type":"bytes","store":"huawei","sent":23560192,"total":62914560}
{"type":"phase","store":"huawei","phase":"submitting"}
{"type":"result","store":"huawei","success":true,"duration_ms":34570}
{"type":"done","apk":{...},"results":[...]}

bytes 事件每 ~100ms 一条(throttled),多家并发各自一条流,按 store 字段区分。Go 父进程消费示例:

cmd := exec.CommandContext(ctx, "apkgo", "upload", "-f", apkPath, "--progress-stream")
out, _ := cmd.StdoutPipe()
cmd.Start()
sc := bufio.NewScanner(out)
for sc.Scan() {
    var evt map[string]any
    json.Unmarshal(sc.Bytes(), &evt)
    switch evt["type"] {
    case "bytes":
        ui.UpdateProgress(evt["store"].(string), evt["sent"].(float64), evt["total"].(float64))
    case "result":
        ui.MarkStoreDone(evt["store"].(string), evt["success"].(bool))
    case "done":
        ui.Finish(evt["results"])
    }
}
cmd.Wait()

嵌入式调用(Go SDK)

apkgo 同时是一个 CLI 一个 Go 库。Cloud worker、自定义 CI 工具、IDE 插件可以直接 import pkg/apkgo 调用上传流程,免去 spawn 子进程 + 解析 stdout 的麻烦。

import (
    "context"
    "github.com/KevinGong2013/apkgo/pkg/apkgo"
    "github.com/KevinGong2013/apkgo/pkg/config"
    "github.com/KevinGong2013/apkgo/pkg/uploader"
)

cfg := &config.Config{
    Stores: map[string]map[string]string{
        "huawei":  {"service_account": vault.Get("huawei-sa-base64")},
        "tencent": {
            "user_id":       "...",
            "access_secret": vault.Get("tencent-secret"),
            "app_id_map":    `{"com.foo":"111","com.bar":"222"}`,
        },
    },
}

result, err := apkgo.Run(ctx, apkgo.Job{
    APKFile:  "https://artifacts.example.com/v1.2.0.apk",
    Stores:   []string{"huawei", "tencent"},
    Notes:    "Bug fixes",
    Config:   cfg,
    Progress: uploader.NopManager,  // or NewNDJSONManager(w) for streamed events
})

特性:

  • 零全局状态apkgo.Run 不动 slog.Default、不设 exit code,安全地在长生命周期进程中调用

  • 支持 URL 输入APKFile / APKFile64 接受本地路径或 http(s) URL(自动 fetch + 临时文件 + 退出清理)

  • 可插拔进度报告uploader.ProgressManager 接口可以接 mpb / NDJSON / 自定义实现

  • Pre-upload 错误才 return errorRun 返回的 error 只覆盖 fetch / 解析 / config 阶段;进入上传后每家的失败都在 Result.Results[i].Error

  • Cloud worker 友好的几个字段

    apkgo.Run(ctx, apkgo.Job{
        // ... config / file / stores / etc
    
        // 1. Per-job logger(cloud 注入 job_id / tenant_id / trace_id)
        Logger: slog.New(handler).With("job_id", id, "tenant", tid),
    
        // 2. 指标 emit hook(store.start / store.end / hook.run)
        Events: func(ev uploader.Event) {
            if ev.Type == uploader.EventStoreEnd {
                prom.UploadCounter.WithLabelValues(ev.Store, string(ev.Result.Category)).Inc()
                prom.UploadDuration.WithLabelValues(ev.Store).Observe(ev.Duration.Seconds())
            }
        },
    })
    

    每家商店还可以在 yaml 里加独立 timeout(覆盖全局 --timeout):

    stores:
      huawei:
        service_account_file: "..."
        timeout: 8m              # 等 submit polling,给宽点
      pgyer:
        api_key: "..."
        timeout: 30s             # 简单上传,超时收紧
    

完整 API 见 pkg/apkgo 的 godoc。

CI/CD 示例

GitHub Actions

- name: Upload to app stores
  env:
    APKGO_HUAWEI_SERVICE_ACCOUNT: ${{ secrets.HUAWEI_SERVICE_ACCOUNT }}  # base64(JSON 凭证)
    APKGO_XIAOMI_EMAIL: ${{ secrets.XIAOMI_EMAIL }}
    APKGO_XIAOMI_PRIVATE_KEY: ${{ secrets.XIAOMI_PRIVATE_KEY }}
    APKGO_XIAOMI_CERT: ${{ secrets.XIAOMI_CERT }}             # base64(.cer 文件)
    APKGO_OPPO_CLIENT_ID: ${{ secrets.OPPO_CLIENT_ID }}
    APKGO_OPPO_CLIENT_SECRET: ${{ secrets.OPPO_CLIENT_SECRET }}
    APKGO_VIVO_ACCESS_KEY: ${{ secrets.VIVO_ACCESS_KEY }}
    APKGO_VIVO_ACCESS_SECRET: ${{ secrets.VIVO_ACCESS_SECRET }}
    APKGO_TENCENT_USER_ID: ${{ secrets.TENCENT_USER_ID }}
    APKGO_TENCENT_ACCESS_SECRET: ${{ secrets.TENCENT_ACCESS_SECRET }}
    APKGO_TENCENT_APP_ID_MAP: ${{ secrets.TENCENT_APP_ID_MAP }}     # 多 app: '{"com.foo":"111",...}'
  run: |
    apkgo upload \
      -f app/build/outputs/apk/release/app-release.apk \
      --notes-file CHANGELOG.md \
      --store huawei,xiaomi,oppo,vivo,tencent \
      --timeout 15m

Docker

docker run --rm \
  -v $(pwd)/apkgo.yaml:/apkgo.yaml \
  -v $(pwd)/app.apk:/app.apk \
  ghcr.io/kevingong2013/apkgo:latest \
  upload -f /app.apk --notes "Bug fixes"

跨机器同步配置

通过加密导出/导入,安全地在多台机器或 CI 间共享商店凭证:

# 机器 A:加密导出
apkgo config export --out config.enc
# 输入密码(或设置 APKGO_CONFIG_KEY 环境变量)

# 提交到私有仓库
git add config.enc && git commit -m "sync config" && git push

# 机器 B:解密导入
git pull
apkgo config import config.enc

CI 中使用环境变量免交互:

- name: Import apkgo config
  env:
    APKGO_CONFIG_KEY: ${{ secrets.APKGO_CONFIG_KEY }}
  run: apkgo config import config.enc

加密方式:AES-256-GCM + scrypt 密钥派生,密码错误会明确提示。

全部命令

apkgo init          [-s store1,store2] [-c config.yaml]
apkgo upload        -f <apk> [--file64 <apk>] [-s stores] [-n notes] [--notes-file path] [--dry-run] [-t timeout]
apkgo doctor        [-s stores] [-f <apk> | -p <package>]
apkgo config export --out <file>
apkgo config import <file>
apkgo stores        [-o json|text]
apkgo history       [-n limit]
apkgo upgrade
apkgo version       [-o json|text]

全局参数

-c, --config        配置文件路径 (默认: apkgo.yaml)
-o, --output        输出格式: json 或 text (默认: json)
-t, --timeout       全局超时 (默认: 10m)
-v, --verbose       详细日志输出到 stderr
    --no-telemetry  禁用匿名使用统计

隐私

apkgo 收集匿名使用统计以改进产品,不收集任何敏感信息

收集不收集
匿名安装 ID (随机 UUID)账号、凭证
使用的商店名称包名、应用名
上传成功/失败APK 文件内容
CLI 使用方式更新日志内容
apkgo 版本、OS/架构IP 地址

关闭方式:--no-telemetryAPKGO_TELEMETRY=off

License

Apache License 2.0