MoreRSS

site iconMT | 面条修改

关注于前端开发的web工程师,Self-hosting Homelab 爱好者,email.ml dns.surf开发者。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

MT | 面条的 RSS 预览

在浏览器中轻松运行 Python 程序

2024-12-21 17:57:34

最近,微软开源了一个名为 MarkItDown 的程序,可以将 Office 文件转换为 Markdown 格式。这个项目一经发布就迅速登上了 GitHub 热门榜。

然而,由于 MarkItDown 是一个 Python 程序,对于非技术用户来说使用起来可能有些困难。为了解决这个问题,我想到了利用 WebAssembly 技术在浏览器中直接运行 Python 代码。

在浏览器内运行 Python 的开源程序是 Pyodide,使用 WebAssembly 移植了 CPython,所以 Python 的语法都是支持的。 Cloudflare 的 Python Worker 也使用的 Pyodide。

Pyodide 是 CPython 的一个移植版本,用于 WebAssembly/Emscripten。

Pyodide 使得在浏览器中使用 micropip 安装和运行 Python 包成为可能。任何在 PyPi 上有可用 wheel 文件的纯 Python 包都被支持。

许多具有 C 扩展的包也已被移植以供 Pyodide 使用。这些包括许多通用包,如 regex、PyYAML、lxml,以及包括 NumPy、pandas、SciPy、Matplotlib 和 scikit-learn 在内的科学 Python 包。Pyodide 配备了强大的 JavaScript ⟺ Python 外部函数接口,使得您可以在代码中自由地混合这两种语言,几乎没有摩擦。这包括对错误处理、async/await 的全面支持,以及更多功能。

在浏览器中使用时,Python 可以完全访问 Web API。

尝试了一下运行 MarkItDown 没想到异常的顺利,看来 WebAssembly 真的是浏览器的未来。

遇到的主要挑战和解决方案:

  1. 文件传输问题:如何将用户选择的文件传递给 Worker 中的 Python 运行时?

    • 解决方案:利用 Pyodide 提供的方案,将浏览器文件转换为 ArrayBuffer,然后写入 Emscripten 文件系统的本地缓存。
  2. 依赖安装问题:PyPI 在中国大陆访问受限。

最终,我们成功实现了一个完全运行在浏览器中的 MarkItDown 工具。欢迎访问 Office File to Markdown 进行体验。

Office File to Markdown

最后放出一下 Worker 中运行 Python 的核心代码:

// eslint-disable-next-line no-undef
importScripts('https://testingcf.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js')

// npmmirror 支持 pyodide ,但是不支持 pyodide 下的 zip 包
// importScripts('https://registry.npmmirror.com/pyodide/0.26.4/files/pyodide.js')

async function loadPyodideAndPackages() {
  // eslint-disable-next-line no-undef
  const pyodide = await loadPyodide()
  globalThis.pyodide = pyodide

  await pyodide.loadPackage('micropip')

  const micropip = pyodide.pyimport('micropip')

  // 需要支持 PEP 691 和跨域, 目前 tuna 支持 PEP 691,但不支持跨域 https://github.com/tuna/issues/issues/2092
  // micropip.set_index_urls([
  //   'https://pypi.your.domains/pypi/simple',  
  // ])

  await micropip.install('markitdown==0.0.1a2')
}

const pyodideReadyPromise = loadPyodideAndPackages()

globalThis.onmessage = async (event) => {
  await pyodideReadyPromise

  const file = event.data
  try {
    console.log('file', file)
    const startTime = Date.now()
    globalThis.pyodide.FS.writeFile(`/${file.filename}`, file.buffer)

    await globalThis.pyodide.runPythonAsync(`
from markitdown import MarkItDown

markitdown = MarkItDown()

result = markitdown.convert("/${file.filename}")
print(result.text_content)

with open("/${file.filename}.md", "w") as file:
  file.write(result.text_content)
`)
    globalThis.postMessage({
      filename: `${file.filename}.md`,
      content: globalThis.pyodide.FS.readFile(`/${file.filename}.md`, { encoding: 'utf8' }),
      time: Date.now() - startTime,
    })
  }
  catch (error) {
    globalThis.postMessage({ error: error.message || 'convert error', filename: file.filename })
  }
}
stat

使用 Cloudflare Snippets 搭建一个不限流量的 Docker 镜像

2024-12-21 16:17:40

Cloudflare Workers 搭建 Docker 镜像个人使用请求数小没啥问题。但是如果公开使用,大量的请求数还是会产生费用。

其实 Cloudflare 还有一个更轻量的 JS Runtime: Cloudflare Snippets, 但是也有更严格的限制:CPU 执行时间 5 ms,最大内存 2M, 最大代码量 32K。 不过拿来重写请求足够了。

遗憾的是 Cloudflare Snippets 目前还未对 Free 计划开放,不过他们博客说 Free 计划可以建 5 个 Snippets

如果你有 Pro 计划,拿 Cloudflare Workers 的代码稍微修改一下就可以运行, 支持 Docker Hub, Google Container Registry, GitHub Container Registry, Amazon Elastic Container Registry, Kubernetes Container Registry, Quay, Cloudsmith。

修改后的代码:

// 原代码: https://github.com/ciiiii/cloudflare-docker-proxy/blob/master/src/index.js

const CUSTOM_DOMAIN = 'your.domains'
const MODE = 'production'

const dockerHub = 'https://registry-1.docker.io'

const routes = {
    // production
    [`docker.${CUSTOM_DOMAIN}`]: dockerHub,
    [`quay.${CUSTOM_DOMAIN}`]: 'https://quay.io',
    [`gcr.${CUSTOM_DOMAIN}`]: 'https://gcr.io',
    [`k8s-gcr.${CUSTOM_DOMAIN}`]: 'https://k8s.gcr.io',
    [`k8s.${CUSTOM_DOMAIN}`]: 'https://registry.k8s.io',
    [`ghcr.${CUSTOM_DOMAIN}`]: 'https://ghcr.io',
    [`cloudsmith.${CUSTOM_DOMAIN}`]: 'https://docker.cloudsmith.io',
    [`ecr.${CUSTOM_DOMAIN}`]: 'https://public.ecr.aws',

    // staging
    [`docker-staging.${CUSTOM_DOMAIN}`]: dockerHub,
}

async function handleRequest(request) {
    const url = new URL(request.url)
    const upstream = routeByHosts(url.hostname)
    if (upstream === '') {
        return new Response(
            JSON.stringify({
                routes,
            }), {
                status: 404,
            },
        )
    }
    const isDockerHub = upstream === dockerHub
    const authorization = request.headers.get('Authorization')
    if (url.pathname === '/v2/') {
        const newUrl = new URL(`${upstream}/v2/`)
        const headers = new Headers()
        if (authorization) {
            headers.set('Authorization', authorization)
        }
        // check if need to authenticate
        const resp = await fetch(newUrl.toString(), {
            method: 'GET',
            headers,
            redirect: 'follow',
        })
        if (resp.status === 401) {
            return responseUnauthorized(url)
        }
        return resp
    }
    // get token
    if (url.pathname === '/v2/auth') {
        const newUrl = new URL(`${upstream}/v2/`)
        const resp = await fetch(newUrl.toString(), {
            method: 'GET',
            redirect: 'follow',
        })
        if (resp.status !== 401) {
            return resp
        }
        const authenticateStr = resp.headers.get('WWW-Authenticate')
        if (authenticateStr === null) {
            return resp
        }
        const wwwAuthenticate = parseAuthenticate(authenticateStr)
        let scope = url.searchParams.get('scope')
        // autocomplete repo part into scope for DockerHub library images
        // Example: repository:busybox:pull => repository:library/busybox:pull
        if (scope && isDockerHub) {
            const scopeParts = scope.split(':')
            if (scopeParts.length === 3 && !scopeParts[1].includes('/')) {
                scopeParts[1] = `library/${scopeParts[1]}`
                scope = scopeParts.join(':')
            }
        }
        return await fetchToken(wwwAuthenticate, scope, authorization)
    }
    // redirect for DockerHub library images
    // Example: /v2/busybox/manifests/latest => /v2/library/busybox/manifests/latest
    if (isDockerHub) {
        const pathParts = url.pathname.split('/')
        if (pathParts.length === 5) {
            pathParts.splice(2, 0, 'library')
            const redirectUrl = new URL(url)
            redirectUrl.pathname = pathParts.join('/')
            return Response.redirect(redirectUrl, 301)
        }
    }
    // foward requests
    const newUrl = new URL(upstream + url.pathname)
    const newReq = new Request(newUrl, {
        method: request.method,
        headers: request.headers,
        redirect: 'follow',
    })
    const resp = await fetch(newReq)
    if (resp.status === 401) {
        return responseUnauthorized(url)
    }
    return resp
}

function routeByHosts(host) {
    if (host in routes) {
        return routes[host]
    }
    if (MODE === 'debug') {
        return dockerHub
    }
    return ''
}

function parseAuthenticate(authenticateStr) {
    // sample: Bearer realm="https://auth.ipv6.docker.com/token",service="registry.docker.io"
    // match strings after =" and before "
    const re = /(?<==")(?:\\.|[^"\\])*(?=")/g
    const matches = authenticateStr.match(re)
    if (matches == null || matches.length < 2) {
        throw new Error(`invalid Www-Authenticate Header: ${authenticateStr}`)
    }
    return {
        realm: matches[0],
        service: matches[1],
    }
}

async function fetchToken(wwwAuthenticate, scope, authorization) {
    const url = new URL(wwwAuthenticate.realm)
    if (wwwAuthenticate.service.length) {
        url.searchParams.set('service', wwwAuthenticate.service)
    }
    if (scope) {
        url.searchParams.set('scope', scope)
    }
    const headers = new Headers()
    if (authorization) {
        headers.set('Authorization', authorization)
    }
    return await fetch(url, {
        method: 'GET',
        headers
    })
}

function responseUnauthorized(url) {
    const headers = new(Headers)()
    if (MODE === 'debug') {
        headers.set(
            'Www-Authenticate',
            `Bearer realm="http://${url.host}/v2/auth",service="cloudflare-docker-proxy"`,
        )
    } else {
        headers.set(
            'Www-Authenticate',
            `Bearer realm="https://${url.hostname}/v2/auth",service="cloudflare-docker-proxy"`,
        )
    }
    return new Response(JSON.stringify({
        message: 'UNAUTHORIZED'
    }), {
        status: 401,
        headers,
    })
}

export default {
    fetch: handleRequest,
}
stat

Cloudflare PyPI Mirror

2024-12-18 21:06:14

Pyodide 是一个在 WebAssembly 中运行 Python 的工具库,使用 Micropip 通过 PyPI 来安装包。由于 WebAssembly 在浏览器内运行需要跨域和 PEP 691,但是清华的 tuna 又不支持 CORS 跨域。

PyPI 在中国大陆是无法正常访问的,但是有许多的 Mirror。清华、阿里云、腾讯云、华为云等不少网站都提供了镜像。这些镜像除了清华的 tuna,其他都不支持 JSON-based Simple API for Python (PEP 691)。

由于 WebAssembly 在浏览器内运行需要跨域和 PEP 691,但是清华的 tuna 又不支持 CORS 跨域。

所以在中国大陆可能没有 Micropip 可用的 PyPI 镜像。

基于这个背景,使用 Cloudflare 搭建了一个支持 PEP691 和 CORS 的 Mirror。

支持 Workers 或者 Snippets 都可以搭建,但各有优缺点:

Workers

优点:免费计划可用。

缺点:会产生很多 Worker 请求,可能超出免费计划后不可用或需要付费。

Snippets

优点:不产生 Worker 请求,支持大量使用。 缺点:Snippets 目前只有 Pro 以上计划使用,Free 不可用。

代码

对应代码已经开源,地址:

https://github.com/ccbikai/cloudflare-pypi-mirror

Cloudflare PyPI Mirror

stat

Vite SSR 项目 Docker 镜像最小化打包方案

2024-08-31 13:14:00

最近准备把部署在 Cloudflare, Vercel, Netlify 上的项目迁移到自己的 VPS 通过 Docker 运行,就复习了一下 Docker 镜像打包。 但是一个很小的项目打包出来就是 1.05GB, 这显然是不能接受的。所以研究了一下 Node.JS 项目 Docker 镜像最小化打包方案, 将镜像大小从 1.06GB 缩小到了 135 MB。

示例项目是一个 Astro 项目, 使用 Vite 作为构建工具, SSR 模式运行。

第 0 版

主要思路是使用最小化系统镜像,选用 Alpine Linux 镜像。

按照 Astro 官方文档服务端渲染模式(SSR), 将基础镜像替换为 node:lts-alpine, NPM 替换为 PNPM, 打包出来的体积是 1.06 GB。 也就是最差的状态。

FROM node:lts-alpine AS base

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

WORKDIR /app

COPY . .
RUN pnpm install --frozen-lockfile
RUN export $(cat .env.example) && pnpm run build

ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD node ./dist/server/entry.mjs
docker build -t v0 .
[+] Building 113.8s (11/11) FINISHED                                                                                                                                        docker:orbstack
 => [internal] load build definition from Dockerfile                                                                                                                                   0.0s
 => => transferring dockerfile: 346B                                                                                                                                                   0.0s
 => [internal] load metadata for docker.io/library/node:lts-alpine                                                                                                                     1.1s
 => [internal] load .dockerignore                                                                                                                                                      0.0s
 => => transferring context: 89B                                                                                                                                                       0.0s
 => [1/6] FROM docker.io/library/node:lts-alpine@sha256:1a526b97cace6b4006256570efa1a29cd1fe4b96a5301f8d48e87c5139438a45                                                               0.0s
 => [internal] load build context                                                                                                                                                      0.2s
 => => transferring context: 240.11kB                                                                                                                                                  0.2s
 => CACHED [2/6] RUN corepack enable                                                                                                                                                   0.0s
 => CACHED [3/6] WORKDIR /app                                                                                                                                                          0.0s
 => [4/6] COPY . .                                                                                                                                                                     2.0s
 => [5/6] RUN pnpm install --frozen-lockfile                                                                                                                                          85.7s
 => [6/6] RUN export $(cat .env.example) && pnpm run build                                                                                                      11.1s
 => exporting to image                                                                                                                                                                13.4s
 => => exporting layers                                                                                                                                                               13.4s
 => => writing image sha256:653236defcbb8d99d83dc550f1deb55e48b49d7925a295049806ebac8c104d4a                                                                                           0.0s
 => => naming to docker.io/library/v0

第 1 版

主要思路是先安装生产环境依赖,产生第一层。 再安装全量依赖,打包生成 JavaScript 产物,产生第二层。 最后将生产环境依赖和 JavaScript 产物复制到运行环境。

按照 多层构建(使用 SSR) 的方案, 将镜像大小缩小到了 306MB,缩小不小,但是这个方案有个缺点,需要明确的制定生产依赖,如果少指定了生产依赖,运行时会报错

FROM node:lts-alpine AS base

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

WORKDIR /app
COPY package.json pnpm-lock.yaml ./

FROM base AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile

FROM base AS build-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile

FROM build-deps AS build
COPY . .
RUN export $(cat .env.example) && pnpm run build

FROM base AS runtime
COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist

ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD node ./dist/server/entry.mjs
docker build -t v1 .
[+] Building 85.5s (15/15) FINISHED                                                                                                                                         docker:orbstack
 => [internal] load build definition from Dockerfile                                                                                                                                   0.1s
 => => transferring dockerfile: 680B                                                                                                                                                   0.0s
 => [internal] load metadata for docker.io/library/node:lts-alpine                                                                                                                     1.8s
 => [internal] load .dockerignore                                                                                                                                                      0.0s
 => => transferring context: 89B                                                                                                                                                       0.0s
 => [base 1/4] FROM docker.io/library/node:lts-alpine@sha256:1a526b97cace6b4006256570efa1a29cd1fe4b96a5301f8d48e87c5139438a45                                                          0.0s
 => [internal] load build context                                                                                                                                                      0.3s
 => => transferring context: 240.44kB                                                                                                                                                  0.2s
 => CACHED [base 2/4] RUN corepack enable                                                                                                                                              0.0s
 => CACHED [base 3/4] WORKDIR /app                                                                                                                                                     0.0s
 => [base 4/4] COPY package.json pnpm-lock.yaml ./                                                                                                                                     0.2s
 => [prod-deps 1/1] RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile                                                                           35.1s
 => [build-deps 1/1] RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile                                                                                 65.5s
 => [runtime 1/2] COPY --from=prod-deps /app/node_modules ./node_modules                                                                                                               5.9s
 => [build 1/2] COPY . .                                                                                                                                                               0.8s
 => [build 2/2] RUN export $(cat .env.example) && pnpm run build                                                                                                                       7.5s
 => [runtime 2/2] COPY --from=build /app/dist ./dist                                                                                                                                   0.1s
 => exporting to image                                                                                                                                                                 4.2s
 => => exporting layers                                                                                                                                                                4.1s
 => => writing image sha256:8ae6b2bddf0a7ac5f8ad45e6abb7d36a633e384cf476e45fb9132bdf70ed0c5f                                                                                           0.0s
 => => naming to docker.io/library/v1

第 2 版

主要思路是将 node_modules 内联进 JavaScript 文件,最终只复制 JavaScript 文件到运行环境。

之前看 Next.JS 的时候,记得可以将 node_modules 内联进 JavaScript 文件,这样就不需要 node_modules 了。 所以就研究了一下,发现 Vite SSR 也是支持的,所以判断 Docker 环境就使用内联的方式,不需要复制 node_modules ,只复制最终的 dist 产物,将镜像大小缩小到 135MB 了。

打包脚本改动:

vite: {
  ssr: {
    noExternal: process.env.DOCKER ? !!process.env.DOCKER : undefined;
  }
}

最终的 Dockerfile 如下

FROM node:lts-alpine AS base

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

WORKDIR /app
COPY package.json pnpm-lock.yaml ./

# FROM base AS prod-deps
# RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile

FROM base AS build-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile

FROM build-deps AS build
COPY . .
RUN export $(cat .env.example) && export DOCKER=true && pnpm run build

FROM base AS runtime
# COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist

ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD node ./dist/server/entry.mjs
 docker build -t v2 .
[+] Building 24.9s (13/13) FINISHED                                                                                                                                         docker:orbstack
 => [internal] load build definition from Dockerfile                                                                                                                                   0.0s
 => => transferring dockerfile: 708B                                                                                                                                                   0.0s
 => [internal] load metadata for docker.io/library/node:lts-alpine                                                                                                                     1.7s
 => [internal] load .dockerignore                                                                                                                                                      0.0s
 => => transferring context: 89B                                                                                                                                                       0.0s
 => [base 1/4] FROM docker.io/library/node:lts-alpine@sha256:1a526b97cace6b4006256570efa1a29cd1fe4b96a5301f8d48e87c5139438a45                                                          0.0s
 => [internal] load build context                                                                                                                                                      0.3s
 => => transferring context: 240.47kB                                                                                                                                                  0.2s
 => CACHED [base 2/4] RUN corepack enable                                                                                                                                              0.0s
 => CACHED [base 3/4] WORKDIR /app                                                                                                                                                     0.0s
 => CACHED [base 4/4] COPY package.json pnpm-lock.yaml ./                                                                                                                              0.0s
 => CACHED [build-deps 1/1] RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile                                                                           0.0s
 => [build 1/2] COPY . .                                                                                                                                                               1.5s
 => [build 2/2] RUN export $(cat .env.example) && export DOCKER=true && pnpm run build                                                                                                15.0s
 => [runtime 1/1] COPY --from=build /app/dist ./dist                                                                                                                                   0.1s
 => exporting to image                                                                                                                                                                 0.1s
 => => exporting layers                                                                                                                                                                0.1s
 => => writing image sha256:0ed5c10162d1faf4208f5ea999fbcd133374acc0e682404c8b05220b38fd1eaf                                                                                           0.0s
 => => naming to docker.io/library/v2

最终对比,体积从 1.06GB 降低到 135MB, 构建时间从 113.8s 降低到 24.9s

docker images
REPOSITORY                         TAG         IMAGE ID       CREATED          SIZE
v2                                 latest      0ed5c10162d1   5 minutes ago    135MB
v1                                 latest      8ae6b2bddf0a   6 minutes ago    306MB
v0                                 latest      653236defcbb   11 minutes ago   1.06GB

示例项目是开源的,可以在 GitHub 查看。

BroadcastChannel

stat

BroadcastChannel - 将你的 Telegram Channel 转为微博客

2024-08-11 17:18:12

之前一直在 X 分享一些有趣的工具,而且也会同步到 Telegram Channel 上。看到 Austin 说准备建立一个网站,把分享内容都收录进去。 刚好想到最近看到的一个模板 Sepia,就想到把 Telegram Channel 转成微博客。

难度不大,主功能一个周末就搞完了。过程中做到了浏览器端 0 JS, 分享一些有趣的技术点:

  1. 防剧透模式和移动端搜索框隐藏展示,使用的 CSS ":checked 伪类" 和 "+ 紧邻兄弟组合器" 实现的,参考

  2. 过渡动画使用的 CSS View Transitions, 参考

  3. 图片灯箱用的 HTML 的 popover 属性,参考

  4. 返回顶部的展示和隐藏使用 CSS animation-timeline 实现 Chrome 115 以上版本专属,参考

  5. 多图瀑布流布局,使用 grid 布局实现,参考

  6. 访问统计使用一个 1px 的透明图片做 LOGO 背景,上古技术了,现在几乎没有访问统计软件支持了

  7. 禁止浏览器端 JS 运行,使用的 Content-Security-Policy 的 script-src 'none' 参考

搞完以后,就直接开源了,很意外有那么多人喜欢,一周就收获了 800+ 的 star 了。

如果你也有兴趣,可以去 GitHub 上看看。

https://github.com/ccbikai/BroadcastChannel

GitHub 上的 BroadcastChannel 仓库

stat

阿里云 AP8220 刷机教程

2024-07-29 21:39:55

最近在准备给家用网络升级 2.5G, 在咸鱼收了一台阿里云 AP8220 来折腾。 但是这个机器的刷机资料太少了。折腾了2天才成功刷机。所以写一篇文档记录一下。

提醒

非必要,不刷机。

刷机有风险,请做好刷砖准备。

任何刷机问题与我无关,我只分享我的刷机过程。

准备工作

  1. 阿里云 AP8220 机器一台,咸鱼基本 200 上下。
  2. DC 12V2A 电源一个,原厂不带电源。
  3. USB 转 Console 线。
  4. tftp32.exe。
  5. Putty。
  6. OpenWrt 固件(目前只有 LEAN 的收费固件,我无法提供,希望后续有更多的人适配)。

进入 Uboot

使用 USB 转 Console 线连接到设备 Console 口, 网线连接到设备 LAN 口。

Putty 设置链接 COM3(取决于你电脑设备USB口,自己去设备管理看下), 波特率设置 115200

设备通电,立即按键盘的 shift+@ 中断启动,即可进入 Uboot, 不行就多试几次。

电脑设置静态IP: 192.168.10.1

刷入大分区

下载文件 mibib.bin 文件和 tftp32.exe 放入同目录,打开 tftp32 后,切换到 192.168.10.1

在 Putty 开始敲命令:

tftpboot mibib.bin
nand device 1
nand erase 0x50000 0x10000
nand write $fileaddr 0x50000 $filesize

刷完以后,断电。

刷入固件

设备重新通电,进入 Uboot。

将固件以 factory.bin 结尾的文件命名为 ap8220.bin 放入 tftp32.exe 同目录。

在 Putty 开始敲命令刷写固件:

tftpboot ap8220.bin
flash rootfs

set boot3 "set mtdparts mtdparts=nand0:0x8000000@0x0(fs)"
set boot4 "ubi part fs && ubi read 42000000 kernel"
set setup1 "partname=1 && setenv bootargs ubi.mtd=rootfs ${args_common}"
set setup2 "partname=2 && setenv bootargs ubi.mtd=rootfs ${args_common}"

saveenv

刷完以后,断电。

重新通电等待后可正常启动。

电脑改回 DHCP 获取 IP 地址, 访问 http://192.168.1.1 进入 Web 界面。

在 Web 界面升级刷入 sysupgrade.bin 文件。

刷机完成。

交流

这个机器的刷机资料很少,我想建一个微信群交流。有兴趣的加我微信 ccbikai

AP8220

stat