# 备份

> 在线拍下配置的快照，一个把已保存的密码带到另一台计算机的导出文件，一次排定在下次启动时执行、而不是当场应用的恢复，以及运行历史索引的重建。

备份是设置页面上**数据**分组里唯一的一行。这张卡片在一切照常运行的时候给工作站的配置拷一份，写出那个把已保存的密码也一起带到另一台计算机的单一文件，列出手上有哪些文件，把其中一份拷贝排定在下次启动时替换掉配置，并在那份运行清单看上去不对劲时重建它的索引。它一开头是“为应用配置（设备、点位、逻辑、仪表画面、设置，以及素材库里的图像、图标和图元）拍下快照，以便回退。已记录的运行及其历史单独存放，备份或恢复都不会影响它们。”这个页面需要**更改设置**权限。

## 一份备份里有什么 {#what-a-backup-contains}

一份备份是配置数据库的一份在线拷贝，用 SQLite 的备份机制取下来，所以即使应用正在写入它也是一致的（预写日志也在内）。那个数据库装着什么，它就装着什么：各个驱动的设备和点位、Logic 地址空间（带着保留数值的变量、动作、触发器、报警）、仪表画面、Process 模型、单元、流程、配方、判定和模板、本地用户和角色、OPC UA 服务器配置，以及这个页面上的各项设置。在数据库文件旁边，工作站把用户素材文件夹（素材库里的图像、图标和图元）拷进一个同级文件夹，这样一次恢复也把图片带回来，可选的那条备注则写进一个附属文件。

这些文件在工作站数据根目录的 `backups` 文件夹里：`config-20260902-141500.db`（那一秒已经有一份时加上 `-2` 后缀）、`config-20260902-141500.assets` 和 `config-20260902-141500.db.note.json`。导出文件是它们旁边的单独一个文件，`config-export-20260902-141500.glabconfig`。

一份备份绝不含有已记录的运行及其文件、运行历史索引、常驻事件库、崩溃标记、日志文件、窗口位置和缩放、账户令牌库，也不含 OPC UA 服务器的各个证书库。数据库里的凭据（设备密码、OPC UA 服务器密码）是按输入它们的那个 Windows 用户和那台机器加密存下来的：一份备份在别处打开时，整份配置都能恢复，但这些凭据一律读作未配置，要重新输入。下面那个导出文件，就是唯一把它们一起带走的文件。

## 创建一份备份 {#creating-a-backup}

| 字段 | 它是什么 | 取值 / 默认 | 效果 |
| --- | --- | --- | --- |
| 备注 | “随备份或导出文件一起保存的备注（可选）”，按钮旁边那个字段。 | 最多 200 个字符；默认为空，每次备份或导出之后清空。 | 备份时写进那个附属备注文件，导出时写进导出文件的头部，并显示在那个文件所在的行下面。 |

| 命令 | 作用 | 什么时候变灰（情形） | 什么时候不画出来（角色） |
| --- | --- | --- | --- |
| 打开文件夹 | 在资源管理器里打开备份文件夹，需要时把它建起来。应答这个请求的是工作站自己的窗口，而且只应答这套安装里确实存在的文件夹：别的一律回一句“这不是这台工作站会打开的文件夹。什么也没有打开。”，文件夹在安装之外和干脆不存在，说的是同一句话，所以这个答复不会让发问的一方多知道什么。Windows 打不开的文件夹则回一句“操作系统没能打开它。” | 在另一台设备的浏览器里：“仅在工作站本机的桌面应用中可用。” | 从不。 |
| 创建备份 | 取下快照，然后在状态栏上报出“备份‘config-….db’已创建。”，或者“备份失败：…”。 | 另一条备份命令正在执行时（卡片会变暗）。 | 从不。 |

创建一份备份是只读的工程工作：单元运行期间它照样跑，而且从不和一次配置改动重叠。运行时停止期间这个页面根本不画出来（见[设置](settings)），所以在运行时回来之前，一条备份命令都够不着。一台配置数据库还不存在的工作站会被拒绝，写着“配置数据库还不存在。”

## 导出到另一台计算机 {#export-for-another-machine}

在备份按钮下面，还有自带标题的第二块，**导出到另一台计算机**，它一开头就说清楚自己为什么存在：“备份不带走已保存的密码：设备密码和 OPC UA 服务器密码只能在这台计算机的这个 Windows 账户下打开。导出文件是一个连密码一起带走的文件，用你在这里选定的密码封存，这样一台新计算机就能整台接管本工作站。请把那个密码妥善保管：它是打开这个文件的唯一凭据，而且在这里无法找回。”

| 字段 | 它是什么 | 取值 / 默认 | 效果 |
| --- | --- | --- | --- |
| 导出文件的密码 | 用来封存这个文件的密码，也是日后唯一能把它打开的东西。下方的**显示密码**勾选框只显示在这里输入的内容。 | 至少 8 个字符；默认为空，每次导出之后清空。 | 由它导出密钥，每一份已保存的凭据都用这个密钥重新封存。这个密码哪儿都不存：一个谁也想不起密码的导出文件，谁都打不开，这个应用也一样。 |
| 再输入一次密码 | 同一个密码再打一遍，好在文件写出去之前发现手滑。这个字段没有自己的**显示密码**勾选框，始终隐藏。 | 必须和旁边那个字段一样。 | 它自己不做别的；两个字段不一致时，导出一直被拒绝。 |

上面那个备注字段是同一个：它也跟着走进导出文件，并显示在那一行下面。

| 命令 | 作用 | 什么时候变灰（情形） | 什么时候不画出来（角色） |
| --- | --- | --- | --- |
| 导出 | 在同一个 `backups` 文件夹里写出一个 `config-export-20260902-141500.glabconfig`，并报出“导出文件‘…’已创建，含 N 份已保存的凭据。”这个 Windows 账户读不出来的凭据会另行计数，用报错的口吻：“导出文件‘…’已创建，含 N 份已保存的凭据。M 份已保存的凭据在这个 Windows 账户下无法读取，被留了下来：请在新计算机上重新输入。” | 另一条备份命令正在执行时（“另一条备份命令仍在执行。”）；密码不足八个字符时（“请为导出文件选择一个至少 8 个字符的密码。”）；两个字段不一致时（“两次输入的密码不一样。”）。 | 从不：写一个导出文件要的正是这个页面本来就要的**更改设置**权限，因为它产出的文件装着工作站保管的每一份凭据，封在导出的人自己选的密码底下。 |

导出文件是一个压缩包，里面装着配置数据库的一份一致拷贝、旁边的用户素材，以及一个头部。配置保护的每一列在出去的路上都由本机保险库读出，再用导出密码重新封存；头部带着盐、工作因子，还有一个用派生密钥封存的常量，够用来分辨密码对不对，此外什么都不透露。哪些列会跟着走，是从配置本身读出来的，所以后来新增的凭据也会跟着走，不用谁去记一份清单。写一个导出文件和做一次备份一样，是只读的工程工作，所以单元运行期间它照样跑；而且它会记进日志，因为工作站保管的每一份凭据的副本，刚刚离开了保险库。

导出文件从下面的列表里恢复，按它那一行的**恢复**。这会问“从导出文件恢复”：“‘…’带着它所在计算机的配置以及随之保存的密码。请输入写入它时使用的密码：这些密码会被放入本机的保险库，配置则在你重启应用时替换当前配置。当前配置会先自动保存，已记录的运行不受影响。”，还有一个**导出文件的密码**字段和它的**显示密码**勾选框；它空着的时候恢复一直被拒绝（“请输入这个导出文件写入时使用的密码。”）。密码不对时什么都不写，对话框也不关，原因写在状态栏上，好让下一次尝试就在原地：“这个密码打不开这个导出文件。它是写出这个文件的那台计算机上定下的密码，在这里没有办法找回。”密码对了，就把导出文件打开成本机的一份普通备份，把它带来的凭据放进本机的保险库，把那份备份排定在下次启动时执行，并报出“‘…’已打开，其中的密码已保存到本机。配置会在下次重启时恢复。”；从这里往后就是下面讲的普通恢复，而留在列表里的那份备份就是到货凭据，所以以后不用导出文件、也不用它的密码，同一次恢复还能再来一遍。导出文件本身绝不会被改动，所以同一个文件在第三台计算机上照样打得开。

这两半都不归代理管：既没有导出的工具，也没法从[代理](agent)页面恢复一个导出文件，因为密码是操作者的，而代理自己编出来或者被交到手里的密码会留在对话记录里。`backup_list` 会给导出文件打上标记（`portableExport`），`backup_restore` 则会拒绝它并点名这个页面：“‘…’是给另一台计算机用的配置导出文件。它带的那些已保存密码，是用写出它时定下的密码封起来的，所以要在设置、备份里恢复，那里可以输入这个密码。”

## 列表 {#the-list}

备份和导出文件共用同一份列表，最新在前，因为它们回答的是同一个问题：这里头我该恢复哪一个。每一行显示这个文件是什么时候创建的（用工作站的日期和时间格式），接着是它是什么（**配置备份**或者**导出到另一台计算机**）、它的名字和它的大小，有备注时还有那条备注。这个大小是这个文件占用的全部，也就是删掉它能腾出来的全部：备份是数据库拷贝、它的备注和它的素材快照，而在用得上图片的工作站上，图片是更大的那一半；导出文件则是那一个压缩包。列表在页面打开时读取，之后跟随工作站自己所做的事：在这里或在另一个窗口里创建的备份和导出文件，或者由代理通过[代理](agent)页面创建的备份，都会立刻出现在列表里。从应用之外放进 `backups` 文件夹的文件，比如手工复制进去的，列表不会察觉：要等页面重新打开时才会出现。

| 命令 | 作用 | 什么时候变灰（情形） | 什么时候不画出来（角色） |
| --- | --- | --- | --- |
| 恢复 | 在备份上先问一句“恢复配置”：“要排定用‘…’在应用重启时替换当前配置吗？ 当前配置会先自动保存。已记录的运行不受影响。”，带是和否。选是就排定这次恢复，并报出“‘…’会在下次重启时恢复。”在导出文件上是同一个决定，只是改成问它的密码，用上面讲过的那个对话框。 | 有单元在运行时（“有单元在运行时配置是锁住的。”）。 | 从不。 |
| 删除 | 先问一句“删除备份”：“要永久删除备份‘…’吗？此操作无法撤销。”选是就移除数据库拷贝、它的备注和它的素材快照，并报出“备份‘…’已删除。”导出文件按它自己的身份、也按跟着一起没的东西来称呼，“删除导出文件”：“要永久删除导出文件‘…’吗？它带着的密码会一起消失。此操作无法撤销。”，随后是“导出文件‘…’已删除。” | 这一份已排定要恢复时（“‘…’已排定要恢复。请先取消待执行的恢复。”）。 | 从不。 |

恢复在下面这几种情况下被拒绝，原因写在状态栏上：有记录正在进行时（“有记录正在进行。请先停止它，再排定配置恢复。”）；这个文件根本不是一份配置备份时（“‘…’不是 Ganter Lab 的配置备份，无法恢复。”）；以及它是由这个版本不认识的数据库写出来的时候，比如一个更新的版本（“‘…’不是这个版本的 Ganter Lab 能恢复的配置备份。它可能属于另一个数据库，或者属于更新版本的应用。”）。删除会拒绝已经排定的那一份：“‘…’已排定要恢复。请先取消待执行的恢复。”留多久是你的事：这里没有任何东西会自己删掉备份，而代理可以创建和列出备份，但绝不能删掉一份。

## 恢复已排定 {#restore-scheduled}

排定这件事会在数据根目录里写下一个标记，卡片显示那条警示提示条**恢复已排定**：“‘…’会在你重启应用时恢复。当前配置会先自动保存。”，带一个**取消**按钮，按下去就移除标记并报出“已取消待执行的恢复。”应用重新启动之前什么都不变。在另一个窗口里排定的恢复，或者代理通过[代理](agent)页面排定的恢复，也会让已经打开的页面显示这条提示条；在别处取消后，提示条随之消失。

下一次启动时，在任何东西打开配置之前，应用会再校验一次这个文件，把即将被替换掉的配置拍成 `config-pre-restore-<timestamp>.db`，备注写“恢复‘…’之前自动拍下的快照。”，连同它自己那份素材拷贝（于是一次恢复本身也是可逆的），换掉数据库文件，备份带着素材快照时把它还原回去，最后清掉标记。工作站启动完成后，一条通知会说明结果：

| 结果 | 通知 | 消息 |
| --- | --- | --- |
| 已应用 | **配置已恢复** | “启动时已应用备份“…”。之前的配置已保存为“…”。” |
| 已应用，但备份里的图片没能拷回去 | 在上一条之后出现 **配置已恢复，但有一条警告** | “配置已恢复，但它的图片文件没能随之恢复。之前的图片已保存在‘…’旁边。” |
| 有一条 Process 运行必须用当前配置续跑：这次恢复仍然排定着，留到之后某一次启动再应用 | **配置恢复已推迟** | “请先结束每一条已续跑的 Process 运行记录，然后重启以应用已排定的恢复。” |
| 备份文件不见了 | **配置恢复失败** | “备份“…”未被应用：…”，理由是“没有名为‘…’的备份。” |
| 文件没能通过校验 | **配置恢复失败** | “备份“…”未被应用：…”，理由是恢复对这个文件给出的那一条（见上文）。 |
| 文件无法复制或替换 | **配置恢复失败** | “备份“…”未被应用：…”，理由是“无法复制或替换配置文件；原因记在诊断日志里。请在设置、备份里重新排定这次恢复。” |

失败的恢复会清掉标记，好让后面的启动不被拖住。数据库一旦换掉，这次恢复就算已经应用，哪怕后面的某一步失败了。标记本身清不掉时，消息末尾会加上“Ganter Lab 无法清除已排定的恢复，所以下次启动时它可能再运行一次。”每一种情况的原因都用英文记在诊断日志里，供技术支持查看。

## 配置已恢复：需要复核 {#configuration-restored-review-required}

一次恢复应用之后，卡片显示第二条警示提示条，**配置已恢复：需要复核**：“请复核恢复后的设置、模型、单元、流程、编码、计数器和标签打印队列；核对历史；只有在历史列表缺失或不一致时才重建运行历史索引。”它一直留着，直到你按下**关闭**。一次恢复自己不做任何调和：队列名称作为配置回来了，但 Windows 打印机不会被发现也不会被纠正，计数器就是备份里的那些。

## 重建运行历史索引 {#rebuild-run-history-index}

| 命令 | 作用 | 什么时候变灰（情形） | 什么时候不画出来（角色） |
| --- | --- | --- | --- |
| 重建 | “通过扫描运行文件重新构建已记录运行的列表。如果恢复或移动文件之后历史列表看上去不完整，可以用它。运行文件本身才是准据。”报出“历史索引已重建：恢复 N 条，移除 N 条（共 N 个运行文件）。”，只有一个运行文件时报出“历史索引已重建：恢复 N 条，移除 N 条（共 1 个运行文件）。”。只有某个文件无法读取时才会出现无法读取的数量：“历史索引已重建：恢复 N 条，移除 N 条，N 条无法读取（共 N 个运行文件）。” | 另一条备份命令正在执行时。 | 从不。 |

这次重扫把没有索引条目的运行文件的条目还回去（从这次运行自己那份冻结的快照来，或者在应用中途死掉时算作一次失败的运行），丢掉文件已经不在的条目，从每一个读得出来的文件重建产量投影，并在只读地辨认过之后跳过一个外来的或者损坏的 `.db`，把它计为无法读取而不去改动它。它随后刷新 Process 页面显示的那些产量快照。它绝不碰一个运行文件里已经记录下来的证据。

这次重扫还会重新读取每一次读得出来的运行在启动时填写的元数据值（序列号、批次等），让[历史](histories-list)列表的搜索能凭这些值找到这次运行。对于索引开始保存这些值之前记录的运行，如果更新后那一次性的后台补录读不了它的文件，也靠重扫找回：在重扫读到它之前，这样的运行只能按名称找到。

## 这一节不做什么 {#what-this-section-does-not-do}

这里没有自动备份或者定时备份，没有留存策略，没有备份到别的位置（请从那个文件夹里把文件拷走），没有部分恢复，也没有运行历史的恢复：已记录的运行是数据根目录下自成一体的文件，索引是从它们重建出来的。导出文件也不是备份到别的位置：它写在同一个文件夹里，把它送到需要它的那台计算机上，仍然是你自己的事。运行的导入和导出在[历史](histories)页面上做。
