> 来源：https://mac-cms.com/cms/cms-disable-upgrade
> 简介：结合 MacCMS V10 真实源码讲清自动升级到底是什么：后台首页请求官方接口检测新版本、Update 控制器从 update.maccms.la 拉升级包解压覆盖源码。再给出两条关法——后台权限/路由屏蔽入口，以及二次开发直接禁用升级 action、注掉版本检测请求、用 Nginx 封升级路径，并提醒关掉后要自己负责打补丁。
> 更新：2026-11-02

# 关闭 MacCMS 的自动升级（含二次开发关法）

改了半天模板、动过几个控制器，结果后台点一下「在线升级」，官方升级包解压一盖，自己改的东西全没了——这是自建 MacCMS 最容易踩的坑之一。本文结合 MacCMS V10（`magicblack/maccms10`）真实源码，先讲清「自动升级/版本检测」到底是怎么运作的，再给出从「后台层面屏蔽入口」到「二次开发彻底禁用」的几种关法。

::: tip 结论来自实际读源码
下面所有文件路径、控制器、接口地址都来自实际阅读 `d:\maccms10` 源码（只读，未改动任何源码文件）。涉及行号处仅作定位参考，不同发行版可能略有偏移，以你本地源码为准。想先看整体架构，见[源码结构分析](/cms/cms-source-analysis)；安全加固见[安全加固](/cms/cms-security)。
:::

## 一、先说清：MacCMS 的「自动升级」到底是什么

很多人以为 MacCMS 会「后台偷偷自己升级」。实际读源码后，这里要先纠正一个概念：**MacCMS V10 没有「无人值守的自动升级」**（不会定时自己拉包覆盖，[定时任务配置](/cms/cms-source-analysis) `application/extra/timming.php` 里没有任何升级任务）。它真正存在的是两件事：

1. **版本检测提示**——登录后台首页时，浏览器会去请求官方接口，比对版本号，有新版就在页面上弹一个「有新版本可用」的提示。这一步是**自动发生**的，容易被误当成「自动升级」。
2. **一键在线升级**——你**手动点**提示框里的「点击进入在线升级」后，后台才会去官方服务器下载升级包、解压覆盖源码、跑数据库升级脚本。这一步要人点，不是自动的。

所以严格说，要「关自动升级」，关的其实是第 1 件（自动跑的版本检测请求）和第 2 件（一键覆盖源码的升级功能）。下面逐个拆。

### 1.1 后台首页的版本检测（自动请求官方）

入口在后台首页模板 `application/admin/view_new/index/index.html`。页面加载时有一段 JS，直接向官方升级服务器发请求：

```js
// application/admin/view_new/index/index.html（版本更新检测片段）
if (typeof MAC_VERSION !== 'undefined' && ...) {
    $.ajax({
        url: 'https://update.maccms.la/v10/?c=check&v=' + MAC_VERSION
             + '&p=' + PHP_VERSION + '&tp=' + THINK_VERSION + '&t=' + Math.random(),
        type: 'GET',
        dataType: 'text',
        success: function (response) {
            // 从返回文本里 eval 出 update_content、解析 new_v
            var newV = ...;
            if (newV > MAC_VERSION) {
                // 显示「有新版本可用」提示
                $('#update-notification, #update-notification-mobile').show();
            }
        }
    });
}
```

要点：

- 请求地址是官方的 `https://update.maccms.la/v10/?c=check&...`，把你的 **CMS 版本号、PHP 版本、ThinkPHP 版本**都带了过去。
- `MAC_VERSION` 来自 `config('version')['code']`，由后台 Base 控制器注入模板（`application/admin/controller/Base.php` 结尾 `$this->assign('MAC_VERSION', ...)`）；版本号本身存在 `application/extra/version.php` 的 `code` 字段。
- 返回的文本会被 `eval` 执行一段 `var update_content = [...]`。这是从官方域名拉回来的脚本在你后台页面里执行——信任的是 `update.maccms.la` 这个第三方升级源。

::: warning 这一步会把信息发给第三方
只要有人打开后台首页，浏览器就会主动连 `update.maccms.la` 并上报版本信息。即使你从不点升级，这个请求也一直在发。想完全不与官方升级源发生关系，就得把这段请求也关掉（见[三、2](#_3-2-注掉后台首页的版本检测请求)）。
:::

### 1.2 一键在线升级：Update 控制器（会覆盖源码）

真正「动你文件」的是 `application/admin/controller/Update.php`。首页提示框里那个「【点击进入在线升级】」链接，指向的就是它：

```
admin.php/admin/update/step1.html?file=maccms10_update
```

`Update` 控制器分三步（`step1` / `step2` / `step3`），落地方式是**拉远程 zip 包解压覆盖源码目录**，不是只更新数据或模板：

| 步骤 | action | 做了什么 |
|------|--------|----------|
| 第一步 | `step1($file)` | 从 `https://update.maccms.la/v10/` 下载 `maccms10_update.zip`，做 SHA1 校验防篡改，然后用 `PclZip` **解压到站点根目录、覆盖同名文件**（`PCLZIP_OPT_REPLACE_NEWER`） |
| 第二步 | `step2()` | 若包里带 `application/data/update/database.php`，执行其中的 SQL，做**数据库结构升级** |
| 第三步 | `step3()` | 清缓存，提示升级完成 |

源码里升级服务器地址是 base64 编码藏起来的，解开就是官方域名：

```php
// application/admin/controller/Update.php 构造方法
$this->_url = base64_decode("aHR0cHM6Ly91cGRhdGUubWFjY21zLmxhLw==")."v10/";
//            解开即 https://update.maccms.la/ + "v10/"
$this->_save_path = './application/data/update/';
```

::: danger 这就是会覆盖你改动的地方
`step1` 用 `PclZip` 把官方包解压覆盖到源码目录。只要官方包里有和你改过的**同名文件**（控制器、框架文件、部分模板），就会被盖掉。你在 `application/` 里改的二开代码、被官方包覆盖的模板，都可能一次升级没了。这正是「关掉它」的核心动机。
:::

补充两个源码细节，帮你判断关哪里才安全：

- **`Update` 控制器被豁免了权限校验**。`application/admin/controller/Base.php` 里，`$this->_cl != 'Update'` 的判断让 Update 控制器**跳过** `check_auth()` 权限检查（第 45 行附近）。也就是说只要登录了后台，不管这个管理员有没有「升级」权限，都能触达升级 action。所以靠「角色权限」不一定能挡住它，想稳妥还得从路由/源码层封。
- **有文件完整性校验**。Base 控制器会用 `md5_file('application/admin/controller/Update.php')` 和 `version.php` 里的 `update_hash` 比对，不一致就 `exit`。这意味着你**直接改 `Update.php` 内容**后，若不同步改 `version.php` 的 `update_hash`，整个后台（除 Update 自身）会被这段校验卡死。下面的二开关法会提醒你怎么绕开这个坑。

### 1.3 顺带区分：这几个「升级」不是一回事

读源码时还会碰到别的「升级/update」，别混了：

- `application/data/update/database.php`：**数据库升级脚本**，只在你跑在线升级第二步时才执行，本身不覆盖源码。
- 后台「应用插件」里的「云插件升级」（`addon/upgrade`，见 `application/admin/common/auth.php`、`AddonCloudService.php`）：那是**插件**的升级，和程序源码在线升级是两套，本文不涉及。
- `application/extra/maccms.php` 里的 `'new_version' => ''`：源码注释说明这个键已废弃（见 `common/behavior/Init.php`），不再用于判断版本，别指望改它能关掉检测。

## 二、后台/配置层面怎么关（不改源码）

如果你只是不想让升级被「误点」，又不想动源码，可以从入口可见性和路由上屏蔽。

::: tip 没有现成的「关闭自动升级」开关
实话实说：**本版本源码里没有一个 `maccms.php` 配置项能一键关掉版本检测或在线升级**。所以「配置层面关」本质上是「屏蔽入口 + 封路由」，不是改个开关。想彻底干净，还是得走[二次开发](#三、二次开发-改源码-层面怎么彻底禁)那一节。
:::

- **别点就不升级**。在线升级要手动点「进入在线升级」才会执行。只要团队约定不点，源码不会被动覆盖。这是最轻量但最不保险的办法（挡不住手滑和新同事）。
- **收紧后台管理员**。虽然 `Update` 控制器本身豁免了权限校验（见上），但进后台首页本身要登录。把后台只给可信的人、入口文件改名（[强制改名](/cms/cms-security)）、开好登录防护，能把能点升级的人缩到最小。
- **用 WAF / Nginx 封升级路径**（最推荐的不改源码办法）。直接在 Web 服务器层把升级 URL 拦掉，谁都点不动：

```nginx
# Nginx：拦掉后台在线升级入口（admin.php 换成你改名后的后台入口文件名）
location ~* /admin\.php/admin/update/ {
    return 403;
}
```

::: warning 路径要用你自己的后台入口名
MacCMS 强制要求把 `admin.php` 改名（见[安全加固](/cms/cms-security)），上面规则里的 `admin.php` 要换成你实际的后台入口文件名，否则拦不到。
:::

## 三、二次开发（改源码）层面怎么彻底禁

想一劳永逸，就从源码层把升级能力拆掉。**改前务必备份、用 Git 管好改动**，并且尽量别碰框架核心（`thinkphp/` 目录），只动 `application/` 下的业务文件。

::: danger 改 Update.php 前先搞清完整性校验
前面说过，Base 控制器会校验 `Update.php` 的 md5 与 `version.php` 的 `update_hash`。如果你**直接改 `Update.php` 的内容**而不同步 `update_hash`，会导致除 Update 外整个后台 `exit`。两种安全做法：
1. 改完 `Update.php` 后，重新算一遍 `md5_file('application/admin/controller/Update.php')`，把结果写回 `application/extra/version.php` 的 `update_hash`；
2. 或者干脆**不改 `Update.php` 的内容**，改用下面「封路由 / 封 Web 服务器」的方式从外部挡住它（推荐，省心且不触发校验）。
:::

### 3.1 禁用在线升级 action（让它直接返回）

最直接的是让 `step1` 不再下载覆盖。但如上所述，直接改 `Update.php` 会触发完整性校验，所以推荐**用路由把这几个 action 劫持到一个安全响应**，而不是改控制器内容。在 `application/route.php` 顶部加规则，把升级地址重定向/拦截：

```php
// application/route.php
use think\Route;

// 把在线升级三步全部封死：命中即 403，不进 Update 控制器
Route::rule('admin/update/:action', function () {
    return response('在线升级已被站长关闭', 403);
});
```

::: tip 为什么优先用路由而不是改控制器
`application/route.php` 是业务配置文件，不在 `Update.php` 完整性校验范围内，改它不会把后台卡死。而且规则集中、好维护、升级包一般也不会覆盖你的自定义路由（但仍建议纳入 Git，万一被盖能一眼看出来）。
:::

如果你确实要改控制器本体（比如要完全删掉下载逻辑），记得**改完同步 `update_hash`**：

```php
// application/admin/controller/Update.php —— step1 开头直接拦住
public function step1($file='')
{
    // 二开：站长已禁用在线升级，避免升级包覆盖自定义代码
    return $this->error('在线升级功能已关闭');
    // …原下载/解压逻辑不再执行…
}
```

改完在命令行算新 hash 并写回 `version.php`：

```bash
php -r "echo md5_file('application/admin/controller/Update.php');"
# 把输出填进 application/extra/version.php 的 'update_hash'
```

### 3.2 注掉后台首页的版本检测请求

要让后台不再偷偷连 `update.maccms.la`，编辑后台首页模板 `application/admin/view_new/index/index.html`，把那段「版本更新检测」的 `$.ajax({...})` 注释或删掉即可：

```js
/* ── 版本更新检测（二开已关闭：不再请求 update.maccms.la） ── */
/*
if (typeof MAC_VERSION !== 'undefined' && ...) {
    $.ajax({ url: 'https://update.maccms.la/v10/?c=check&v=' + MAC_VERSION + ... });
}
*/
```

这段在模板里，不受 `Update.php` 完整性校验影响，改它是安全的。关掉后，后台首页就不再上报版本信息，也不会再弹「有新版本可用」。

::: warning 升级包可能盖回模板
`view_new/index/index.html` 这类模板文件，有可能被官方升级包覆盖。既然你已经把在线升级也关了（3.1），一般不会再被盖；但仍建议把这类改动纳入 Git，方便发现被改回。
:::

### 3.3 从菜单/提示上彻底隐藏入口

版本检测关掉后（3.2），「有新版本可用」的提示本身就不会出现，升级入口自然看不到。如果你只想隐藏入口、保留检测，也可以在同一个模板里把 `#update-notification`、`#update-notification-mobile` 两个元素或 `showUpdateDialog()` 调用去掉。注意 `Update` 控制器在菜单权限表 `application/admin/common/auth.php` 里**并没有独立菜单项**（它靠首页提示框的链接触达），所以不用去菜单里找它。

### 3.4 用 Web 服务器兜底（和不改源码那节同一招）

即便做了上面的改动，也建议在 Nginx/WAF 上再封一层升级路径（见[二](#二、后台-配置层面怎么关-不改源码)里的 Nginx 规则）。源码改动可能被升级包覆盖，Web 服务器规则不会，双保险最稳。

## 四、为什么要关，以及关掉后的责任

关掉自动升级的理由，核心就一个字：**稳**。

- **保护二开成果**：在线升级会用官方包覆盖同名文件，你改过的控制器、模板、框架补丁都可能被盖掉。关掉它，改动才稳得住。
- **不被第三方升级源左右**：版本检测和升级包都来自 `update.maccms.la`。关掉后，你的后台不再主动连第三方、不再执行从远端 `eval` 回来的脚本，减少供应链风险面。
- **可控的发布节奏**：升级什么、什么时候升、升之前先在测试环境验证，全部由你决定，而不是被一个「有新版本」的红点推着走。

::: danger 关掉不等于不用管安全
关掉自动升级后，**打补丁的责任就落到你自己头上了**。框架和 CMS 仍会不断修漏洞（尤其 ThinkPHP5 历史上的 RCE / SQL 注入系列），你得自己盯官方仓库、手动把安全补丁合进来。具体做法见[安全加固 · 及时升级打补丁](/cms/cms-security#及时升级打补丁)——那里讲的「及时升级」和本文「关掉自动升级」不矛盾：关的是「自动覆盖式升级」，但安全补丁要你用可控的方式亲手打上。
:::

推荐的落地姿势：**关掉自动覆盖式升级 + 用 Git 管理源码 + 盯官方仓库手动合安全补丁**。这样既不会被升级包毁掉二开，又不至于漏掉关键的安全修复。

## 五、小结

- MacCMS V10 没有「无人值守的自动升级」，真正存在的是：后台首页**自动的版本检测请求**（连 `update.maccms.la`）+ 需**手动点**的**一键在线升级**（`Update` 控制器拉包解压覆盖源码）。
- 入口文件：版本检测在 `application/admin/view_new/index/index.html`；在线升级在 `application/admin/controller/Update.php`（升级地址 base64 藏的 `https://update.maccms.la/v10/`）。
- 没有现成配置开关。不改源码就用 Nginx/WAF 封 `admin/update/` 路径；要彻底禁就用 `application/route.php` 劫持升级 action、注掉首页检测请求，直接改 `Update.php` 则务必同步 `version.php` 的 `update_hash`。
- 关掉后记得自己盯补丁，别把「关自动升级」做成「从此不管安全」。

源码无法 100% 确认的细节（比如官方升级服务器返回格式、具体行号），以你本地实际版本为准。
