保护机器人代码 — Git、PyInfra 与幂等部署¶
这一章讲什么: 机器人上的代码和配置会怎么丢、怎么坏;然后给三层防护: ① 代码住在你的电脑上,改完同步到机器人(机器人不再是唯一副本); ② 用 Git 给代码记历史,改坏了能回滚、能对比; ③ 用 PyInfra 把「更新软件、装依赖、同步代码」写成脚本,SD 卡彻底报废也能在新卡上重建一切。 这一章的工具链是后面每一章的开场动作:改代码 → pyinfra 同步 → 在 Pi 上跑。
1. 先看现象:代码是怎么没的¶
作者先讲了自己的事故,因为「这本书里的每一道防线都是被烫出来的」:
他在带着机器人去参展的前一周,丢过花了数周做的东西——「我学到要非常认真地对待这件事」1。
代码和它的近亲配置(configuration)——系统设置、额外软件、数据文件——一样, 都要花时间研究、设计、测试、调试,丢了就是实打实的工时损失2。
那它们会怎么死?书里列了两大类,每一类都有真实机制:
第一类:SD 卡损坏。 「SD 卡损坏」指卡上存的代码、系统、一切准备的东西都坏了—— 文件读不出来,或者整张卡报废,信息永久丢失3。诱因有三个:
- Pi 意外断电(呼应第 03 章的
sudo poweroff警告); - Pi 发烫、满负荷运转,会慢慢地「烤熟」SD 卡;
- GPIO 引脚或电源上发生电气事故;另外卡本身就小,从 Pi 里取出来随手一放就找不到了3。
第二类:改出来的祸。 写代码就是试错:有的改动成了,有的把东西弄坏, 这时你想回到一个已知能用的版本,看看这期间到底改了什么; 一行错误的配置(比如网络配错)能让机器人再也连不上网、开不了机; 一次系统升级可能让现有代码直接跑不了、或要大改才能再跑4。
这两类还会合体酿成真噩梦:书里举了两个亲历——一次代码改动让机器人把自己 搞坏了,顺带毁了 SD 卡;一次升级系统时碰掉了电源线,SD 卡损坏、系统报废, 而那是机器人大赛前两周,重建过程「很痛苦」5。 他的原话总结:「许多建造者都要吃过几次亏才学会——我们试试走一条容易的路。」5
2. 顶层全景:三层防线¶
威胁 防线 机制
SD 卡损坏/丢失 → ① 代码放 PC + 同步 机器人上永远不是唯一副本
改坏了想反悔 → ② Git 历史 每次改动一个「检查点」,可回滚可对比
系统/配置重装 → ③ PyInfra 部署脚本 把「装好一台机器人」写成可重放的脚本
图说:三层各管一类灾难;③ 还能在全新 SD 卡上把 ①② 的产物一键重建。
3. 核心原理之一:策略一——代码的第一现场是你的电脑¶
有了第 03 章的 SSH 通道,就能让代码反过来流:不在 Pi 上写,在电脑上写,然后传上去6。 好处书里列了三条:可以用顺手的编辑器(VS Code、PyCharm,都有内置终端); 电脑上始终有一份最新副本;有些错误在传上去之前就能在编辑器里看出来6。
传文件的工具有很多(ftp、scp、rsync……),书里选了 PyInfra—— 一个「在远程系统(比如 Pi)上执行命令、完成设置」的 Python 工具7。 第一次同步的完整链路是本章的主线例子,一步步走:
第一步:建项目骨架。 电脑上建项目文件夹(如 my_robot_project),
里面建一个 robot/ 子文件夹,再放第一个测试文件 robot/hello.py,内容一行:
(print 是 Python 的打印函数,括号里那串带引号的字符叫字符串(string)——
就是「一串字符」8。)
第二步:写清单(inventory)。 PyInfra 需要知道机器人是谁——在项目文件夹里建
inventory.py:
用你自己的主机名和用户名。顺手认三个 Python 词:外面是列表(list)(一列目标,
虽然只有一个),每项是元组(tuple)(主机名 + 属性),花括号里那组
"键": "值" 配对叫字典(dictionary)9。这三个数据结构,后面所有章节天天见。
第三步:同步。 在项目文件夹里执行:
读法:pyinfra <清单> <操作>——用 inventory.py 里的目标清单,执行 files.sync 操作,
把本地 robot/ 同步到机器人的 robot/10。PyInfra 的输出本身就是状态报告:
--> Connecting to hosts...
[learnrob3.local] Connected
--> Proposed changes:
[learnrob3.local] Operations: 1 Change: 1 No change: 0
--> Results:
[learnrob3.local] Changed: 1 No change: 0 Errors: 0
注意最后两行的对照:第一次跑 Changed: 1;什么不改再跑一遍,就是 No change: 110。 它先评估「要不要变」,要变才变——这个性质有个名字,第 5 节讲。
第四步:上机器人验证。 SSH 登录,跑:
控制台印出这行字,说明整条链路通了:电脑上的代码已经躺在机器人的 SD 卡上, 并被机器人的 Python 执行11。
第五步:固化成脚本。 每次都敲长命令会烦,把它收进 deploy/update_code.py:
from pyinfra.operations import files
files.sync(src="robot", dest="robot", delete=True, exclude=("*.pyc", "__pycache__", ".vscode"))
三处新东西,都值得当场理解:from pyinfra.operations import files 是 Python 的
导入(import)——把别人写好的功能拿进自己的代码用;delete=True 表示远端多余的、
本地已删的文件也一并删掉(改名、删文件都能对上);exclude 列出的
(*.pyc、__pycache__、.vscode)是 Python 缓存和编辑器文件,不传12。
以后同步就一句话:pyinfra inventory.py deploy/update_code.py12。
排错:五种报错对五种病¶
书里把常见报错做成了对照表,值得原样接住:
| 报错 | 病因 |
|---|---|
pyinfra: command not found |
没装 pip install pyinfra,或装完没重启终端 |
Usage: pyinfra [OPTIONS] INVENTORY OPERATIONS |
漏写了 inventory.py |
Could not resolve hostname |
主机名不对、机器人没开机、不在同一网络 |
Authentication error |
清单里的用户名和 SSH 用的不符;或 SSH 密钥没配对(回第 03 章查) |
No deploy file / 内部异常 |
脚本文件名、位置、拼写有误,或脚本内容写错13 |
4. 核心原理之二:策略二——Git,给代码一台时间机器¶
策略一解决了「SD 卡死了代码陪葬」。但还有个问题没解决:改坏了怎么反悔? 答案是用了很多年的老工具 Git——一个源代码控制系统: 它保存你改动过的历史,你能查看每次改了什么、恢复旧版本、 给每次改动留一句「为什么改」的注释,还能把代码存到多个地方防硬盘报废14。
Git 里的核心名词有两个:仓库(repository,repo)——代码和它的历史存放的地方; 分支(branch)——代码的副本,可以拿去试想法,成了再并回主分支15。
照原书的顺序走一遍最小操作流(每一步旁边是你会看到的真实输出):
① 配身份。 Git 要知道改动是谁的:
② 建仓库。 在项目文件夹里:
git init && git checkout -b main
Initialized empty Git repository in C:/Users/danny/workspace/my_robot_project/.git/
③ 把文件收进来。 git add inventory.py robot/hello.py deploy/update_code.py。
④ 看一眼待提交内容。 git status 会告诉你哪些文件「已暂存待提交」,
哪些还「未跟踪」(.vscode、调试日志这类红色项不用管,正常)16。
⑤ 提交——历史的一个「检查点(checkpoint)」。
-m 是这次改动的留言(以后翻历史全靠它);那串 d9d9995 是这次提交独一无二的编号16。
⑥ 看历史。 git log 列出所有检查点,最新的在最上面。
接下来演示「时间机器」怎么用。把 hello.py 改成:
(import socket 引入 Python 的网络主机名功能;f"..." 是 f-string,
花括号里的表达式会算出来嵌进字符串。)同步到 Pi 再跑,输出变成 <主机名> is alive!。
对比改动:git diff——Git 用自己的格式显示差异:
读法:减号是拿掉的行,加号是新增的行17。
确认无误就 git add + git commit -m "Show the robot hostname",
git log 里现在躺着两个检查点17。
反悔:假设你不小心在 deploy/update_code.py 里敲进了一个词 whoops 还没提交——
git diff 能立刻看见它;不想要?一条 git checkout deploy/update_code.py,
文件就回到上次提交的样子18。再 git diff,没有任何输出 = 干净了。
这就是 Git 给机器人开发的安全带:敢改,因为随时回得去。 书的叮嘱只有一句: 勤提交——尤其是在「刚调通什么东西」之后19。
5. 核心原理之三:策略三——PyInfra 把「一台机器人」写成脚本¶
Git 和同步管住了代码,但管不住系统:SD 卡要是彻底报废,
重装 Raspberry Pi OS 之后,那些手动敲过的 apt 命令、装过的依赖,谁替你记着?
书里早就埋了答案:PyInfra 不止能传文件,它能管理 Pi 上的配置和软件, 让你以无头方式自动化一切,出事时能重建20。具体做法是写三个脚本 + 一个总指挥:
deploy/update_packages.py——更新系统包:
from pyinfra.operations import apt
apt.update(name="Update apt cache", _sudo=True)
apt.upgrade(name="Upgrade all packages", _sudo=True)
PyInfra 由一个个操作(operation)搭成;apt.update 刷新「哪些包可更新」的清单,
apt.upgrade 真正更新;_sudo=True 表示以超级用户执行(装系统包必须)21。
漏写 _sudo=True 的典型报错书里也给了:Could not open lock file ... Permission denied——
普通用户没权更新系统,得 sudo22。
deploy/base_packages.py——装基础依赖:
apt.packages(
name="Install python pip and i2c tools",
packages=["python3-pip", "python3-smbus", "i2c-tools"],
_sudo=True,
)
三个包各有分工:python3-pip 装 Python 包;python3-smbus 访问 I²C 总线;
i2c-tools 调试 I²C23。注意这个操作的语义:它确保一组包存在,只在缺的时候才装——
所以第二遍跑,输出就是 No changes23。
deploy/deploy_all.py——一键重建:
把三个脚本串起来,一次跑完全部操作。书里的真实输出值得读一遍:
--> Proposed changes:
[learnrob3.local] Operations: 4 Change: 2 No change: 2
--> Starting operation: Update apt cache [learnrob3.local] Success
--> Starting operation: Upgrade all packages [learnrob3.local] Success
--> Starting operation: Install python and i2c tools [learnrob3.local] No changes
--> Starting operation: Files/Sync (...) [learnrob3.local] No changes
注意分工:update/upgrade 每次都跑(它俩本来就要检查有没有新更新,不算「改」), 而装包和同步是「没变就不动」24。
「没变就不动」有个正式名字:幂等(idempotency)。 书里给的定义: 只在做出的改变确实需要的时候,才做出改变25。 为什么它是部署脚本的命根子?因为脚本会被跑一百遍(每次改代码都跑), 如果每条命令都无条件重跑,装包会重装、文件会重写,又慢又危险; 幂等让脚本「跑到第 N 次」和「第一次跑」结果一样—— 脚本描述的是「目标状态」,不是「动作步骤」。 这是基础设施即代码(Infrastructure as Code)思想的入门, 书里没提这个词,但整个 deploy 文件夹就是它的雏形。
最后把这一切收进 Git:git add deploy/*.py → git commit -m "More deployment tools"。
书的提醒很到位:虽然直接在 Pi 上手改很诱人,但忍住——只有这样,
你才能随时回答「我刚做了什么?」「那个当时是怎么调通的?」「现在为什么不工作了,改了啥?」26。
6. 作者的判断与证据¶
- 判断: 三条策略的顺序是有设计的——先解决「唯一副本」(同步), 再解决「改坏反悔」(Git),最后解决「系统重建」(PyInfra),一层比一层接近 「灾备恢复」。这不是书里明说的,但章末总结照这个顺序复述了一遍27。
- 亲历证据: 两次事故(自损 + SD 卡毁;断电毁卡毁系统、赛前两周重建)是全书 最有说服力的安全课——比任何抽象劝告都管用5。
- 事实层: PyInfra 的命令模式
pyinfra <inventory> <operations>、 apt 锁文件报错的成因、Git 命令的输出格式,都是可直接上机复核的1022。 - 自留地: 书里明说「这一节只能触及 Git 的表面」,把深入指给 Further reading (GitHub 的 Git Handbook 等)28——诚实的范围声明。
7. 边界与局限¶
- Git 只教到「能自保」。 分支合并、协作流程、.gitignore、远程仓库(GitHub) 这些一概浅尝——够保护机器人项目,不够团队开发。
- PyInfra 脚本不含 SD 卡镜像备份。 系统层面的备份书里预告了「SD card backups」 但本章没落脚本——真要整卡备份,得另找工具(第 18 章才回头提)。
- 一切假设单机单机器人。 inventory 里只列一台;多台机器人的编排超出本章。
- 没讲 secrets 管理。 密码、密钥将来进了配置怎么办,本章没有答案。
8. 可带走的¶
- 代码会死于两种病:SD 卡损坏(断电、过热、电气事故、卡 itself 丢了)和改出来的祸(改坏、配错、升级翻车)——且二者会叠加345。
- 防线三层:PC 为第一现场(机器人不是唯一副本)、Git 记历史(敢改的底气)、PyInfra 脚本(灾后可重建)61420。
pyinfra inventory.py <脚本>的读法:清单 × 操作;输出里Changed/No change本身就是状态诊断10。- 部署脚本里的报错几乎都能对回病因:command not found、漏 inventory、主机名错、认证错、脚本拼写错13。
- Git 最小循环:
add → status → commit -m → log;改坏未提交就checkout <文件>回到上次提交1618。 - 勤提交,尤其是刚调通什么之后19。
- 幂等 = 只在需要改变时才改变;写部署脚本时描述目标状态,不堆动作步骤25。
apt.update/upgrade每次都跑(它在「查」),装包类操作只在缺了才动24。- 忍住别直接在 Pi 上手改——脚本和 Git 让你回答得出「改了什么、为什么、什么时候」26。
9. 原文地图¶
| 主题 | 原书位置 |
|---|---|
| 作者的事故与教训 | text/43-fm-understanding-how-code-can-be-broken-or-lost.txt:13(搜「a week before taking」) · text/43-fm-understanding-how-code-can-be-broken-or-lost.txt:42(搜「knocked the power cable out」) |
| SD 卡损坏的三种诱因 | text/43-fm-understanding-how-code-can-be-broken-or-lost.txt:26(搜「slowly bake an SD card」) |
| 改坏想反悔 | text/43-fm-understanding-how-code-can-be-broken-or-lost.txt:36(搜「go back to a known working version」) |
| PyInfra 是什么、同步命令 | text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:43(搜「runs shell and other commands」) · text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:76(搜「files.sync src=robot dest=robot」) |
| hello.py 上机验证 | text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:118(搜「Raspberry Pi is alive」) |
| update_code.py 与幂等 | text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:132(搜「delete=True」) · text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:148(搜「This property is known as」) |
| 五种排错 | text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:158(搜「command not found」) |
| Git 定义与最小循环 | text/45-fm-strategy-2-using-git-to-go-back-in-time.txt:8(搜「keeps a history of changes」) · text/45-fm-strategy-2-using-git-to-go-back-in-time.txt:60(搜「checkpoint」) |
| diff 与 checkout 回滚 | text/45-fm-strategy-2-using-git-to-go-back-in-time.txt:95(搜「git diff」) · text/45-fm-strategy-2-using-git-to-go-back-in-time.txt:165(搜「git checkout deploy/update_code.py」) |
| 勤提交叮嘱 | text/45-fm-strategy-2-using-git-to-go-back-in-time.txt:183(搜「commit frequently」) |
| PyInfra 操作与 apt 脚本 | text/46-fm-strategy-3-automating-raspberry-pi-setup-with-a-.txt:38(搜「built out of operations」) · text/46-fm-strategy-3-automating-raspberry-pi-setup-with-a-.txt:74(搜「Could not open lock file」) |
| 幂等的正式定义 | text/46-fm-strategy-3-automating-raspberry-pi-setup-with-a-.txt:143(搜「This is idempotency」) |
| 一键重建与总结 | text/46-fm-strategy-3-automating-raspberry-pi-setup-with-a-.txt:156(搜「deploy_all」) · text/47-fm-summary.txt:18(搜「powerful, reliable, and repeatable」) |
-
出处:「Understanding how code can be broken or lost」第 13 段(
text/43-fm-understanding-how-code-can-be-broken-or-lost.txt:13,搜「a week before taking」)。原文:在带机器人去参展前一周,作者经历了数周工作的丢失,「我学到要非常认真地对待这件事」。 ↩ -
出处:「Understanding how code can be broken or lost」第 10 段(
text/43-fm-understanding-how-code-can-be-broken-or-lost.txt:10,搜「takes time and hard work」)。原文:代码和配置都需要研究和学习、设计、制作、测试和调试。 ↩ -
出处:「Understanding how code can be broken or lost」第 20 段(
text/43-fm-understanding-how-code-can-be-broken-or-lost.txt:20,搜「SD card corruption is when」)与第 20 段(搜「slowly bake an SD card」)。原文:损坏即卡上数据破坏、文件不可读或卡报废;诱因含意外断电、发烫的 Pi 慢慢烤坏卡、GPIO/电源电气事故,以及卡太小随手丢失。 ↩↩↩ -
出处:「Understanding how code can be broken or lost」第 36 段(
text/43-fm-understanding-how-code-can-be-broken-or-lost.txt:36,搜「go back to a known working version」)与第 38 段(搜「render your robot useless」)。原文:写代码即试错,想回到已知可用版本看差异;错误配置可致无法联网/开机;升级可能让代码跑不了或大改。 ↩↩ -
出处:「Understanding how code can be broken or lost」第 42 段(
text/43-fm-understanding-how-code-can-be-broken-or-lost.txt:42,搜「knocked the power cable out」)。原文:代码改动让机器人自损并毁了 SD 卡;一次升级中碰掉电源线,卡毁系统坏,距大赛两周,重建痛苦;「许多建造者要吃过几次亏才学会——我们试试容易的路」。 ↩↩↩↩ -
出处:「Strategy 1 – Keeping code on a PC and uploading it」第 10 段(
text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:10,搜「transfer our files」)。原文:用 SSH 桥传文件,在电脑上写再上传;可以自选编辑器、享有一份以上的副本。 ↩↩↩ -
出处:「Strategy 1 – Keeping code on a PC and uploading it」第 43 段(
text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:43,搜「runs shell and other commands」)。原文:PyInfra 在远程系统上执行命令、完成设置;它能做改动,也能侦测是否需要改动。 ↩ -
出处:「Strategy 1 – Keeping code on a PC and uploading it」第 39 段(
text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:39,搜「a bunch of characters」)。原文:print 把文字打到控制台;括号里是参数,这个例子里唯一参数是字符串——引号里的一串字符。 ↩ -
出处:「Strategy 1 – Keeping code on a PC and uploading it」第 72 段(
text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:72,搜「known as a dictionary」)。原文:robots 是目标列表;每项是元组(主机名 + 属性);花括号里 "键": "值" 配对叫字典。 ↩ -
出处:「Strategy 1 – Keeping code on a PC and uploading it」第 76 段(
text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:76,搜「files.sync src=robot dest=robot」)。原文:同步本地 src=robot 到远端 dest=robot;输出显示 Change: 1;再跑则 No change: 1,直到文件再变。 ↩↩↩↩ -
出处:「Strategy 1 – Keeping code on a PC and uploading it」第 118 段(
text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:118,搜「Raspberry Pi is alive」)。原文:SSH 登录后 python robot/hello.py,输出 Raspberry Pi is alive!。 ↩ -
出处:「Strategy 1 – Keeping code on a PC and uploading it」第 132 段(
text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:132,搜「delete=True」)。原文:files.sync 带 delete=True 处理改名删除,exclude 排除 *.pyc、pycache、.vscode;用 pyinfra inventory.py deploy/update_code.py 执行;import 即从别的代码引入功能。 ↩↩ -
出处:「Strategy 1 – Keeping code on a PC and uploading it」第 158 段(
text/44-fm-strategy-1-keeping-code-on-a-pc-and-uploading-it.txt:158,搜「command not found」)。原文列五种排错:command not found(没装/没重启终端)、Usage 提示(漏 inventory.py)、Could not resolve hostname(主机名/关机/网络)、Authentication error(用户名或 SSH 密钥)、No deploy file/内部异常(文件名位置拼写或内容错)。 ↩↩ -
出处:「Strategy 2 – Using Git to go back in time」第 8 段(
text/45-fm-strategy-2-using-git-to-go-back-in-time.txt:8,搜「keeps a history of changes」)。原文:Git 是流行的源代码控制系统,保存改动历史;可审查、恢复旧版本、留注释日志,还能多处存放防硬盘故障。 ↩↩ -
出处:「Strategy 2 – Using Git to go back in time」第 15 段(
text/45-fm-strategy-2-using-git-to-go-back-in-time.txt:15,搜「repositories, or repos」)。原文:Git 把代码和历史存进仓库(repo);可以建分支(代码的副本)试想法,之后并回主分支。 ↩ -
出处:「Strategy 2 – Using Git to go back in time」第 60 段(
text/45-fm-strategy-2-using-git-to-go-back-in-time.txt:60,搜「checkpoint」)。原文:commit 在历史里做一个「checkpoint」;-m 指定日志消息;d9d9995 这类编号每次提交唯一;git status 显示未跟踪文件(.vscode、日志)属正常。 ↩↩↩ -
出处:「Strategy 2 – Using Git to go back in time」第 95 段(
text/45-fm-strategy-2-using-git-to-go-back-in-time.txt:95,搜「git diff」)。原文:git diff 用 -/+ 行显示差异;示例把 print 换成 import socket + f-string 打印主机名;add + commit 后 git log 可见两个版本。 ↩↩ -
出处:「Strategy 2 – Using Git to go back in time」第 165 段(
text/45-fm-strategy-2-using-git-to-go-back-in-time.txt:165,搜「git checkout deploy/update_code.py」)。原文:对已提交文件的错误改动(示例里混入 whoops),git checkout <文件> 把它带回上次提交的版本;git diff 无输出即干净。 ↩↩ -
出处:「Strategy 2 – Using Git to go back in time」第 183 段(
text/45-fm-strategy-2-using-git-to-go-back-in-time.txt:183,搜「commit frequently」)。原文:「Just remember to commit frequently – especially after making something work!」。 ↩↩ -
出处:「Strategy 3 – Automating Raspberry Pi setup with a script」第 9 段(
text/46-fm-strategy-3-automating-raspberry-pi-setup-with-a-.txt:9,搜「automate everything, as a headless system」)。原文:PyInfra 可管理 Pi 上支撑机器人的配置和软件;可以无头地自动化一切,并在全盘崩溃时重建。 ↩↩ -
出处:「Strategy 3 – Automating Raspberry Pi setup with a script」第 38 段(
text/46-fm-strategy-3-automating-raspberry-pi-setup-with-a-.txt:38,搜「built out of operations」)。原文:PyInfra 由操作搭成;apt.update 更新包索引,apt.upgrade 按索引升级;二者都带 _sudo=True 以超级用户运行。 ↩ -
出处:「Strategy 3 – Automating Raspberry Pi setup with a script」第 74 段(
text/46-fm-strategy-3-automating-raspberry-pi-setup-with-a-.txt:74,搜「Could not open lock file」)。原文排错:见到 Could not open lock file ... Permission denied = 漏了 _sudo=True;普通用户没权,须 sudo 成超级用户。 ↩↩ -
出处:「Strategy 3 – Automating Raspberry Pi setup with a script」第 106 段(
text/46-fm-strategy-3-automating-raspberry-pi-setup-with-a-.txt:106,搜「only change if they are missing」)。原文:packages 操作确保一组包存在,只在缺失时安装;python3-pip 装 Python 包、python3-smbus 访问 I²C、i2c-tools 调试 I²C;再跑显示 No changes。 ↩↩ -
出处:「Strategy 3 – Automating Raspberry Pi setup with a script」第 185 段(
text/46-fm-strategy-3-automating-raspberry-pi-setup-with-a-.txt:185,搜「collects the 4 operations」)。原文:deploy_all.py 收集 4 个操作一次跑;update/upgrade 总会运行(它们在查更新,本就不幂等),装包和同步显示 No changes——正确,因为没改动。 ↩↩ -
出处:「Strategy 3 – Automating Raspberry Pi setup with a script」第 143 段(
text/46-fm-strategy-3-automating-raspberry-pi-setup-with-a-.txt:143,搜「This is idempotency」)。原文:「This is idempotency, an important concept that means our PyInfra deployments only make changes when a change is needed.」 ↩↩ -
出处:「Strategy 3 – Automating Raspberry Pi setup with a script」第 12 段(
text/47-fm-summary.txt:12,搜「What did I」)。原文:忍住别在 Pi 上手改,这样脚本能回答「我刚做了什么?」「当时怎么调通的?」「现在为什么不工作了,改了什么?」。 ↩↩ -
出处:「Summary」第 25 段(
text/47-fm-summary.txt:25,搜「fearless about changes」)。原文总结三层:电脑上编辑(多副本 + 强编辑器)、Git 回时间、PyInfra 把全新系统变成机器人的副本。 ↩ -
出处:「Challenges」第 15 段(
text/48-fm-challenges.txt:15,搜「Git Handbook」)。原文 Further reading:GitHub 的 Git Handbook、Packt 的 Git Essentials 视频、github.com/git-guides、PyInfra 官方文档。 ↩