[{"content":"一个后台程序要一直跑着，挂了自动拉起来；另一个程序要每 10 分钟跑一次，跑完就退出。这两件事在 Linux 上都归 systemd 管，前者写成 .service，后者要 .service + .timer 一对。\n本文把这两种单元的每个字段拆开讲清楚，说明为什么定时任务用 timer 比 crontab 更合适，以及命令实际跑在 Docker 容器里时最容易踩的那个坑。\n一、先分清楚 service 和 timer 很多人第一次配 timer 时会困惑：「定时任务为什么要写两个文件？」\n因为这是两件事：\n.service 回答「这个程序该怎么跑」——跑什么命令、用哪个用户、挂了要不要重启、超时多久算失败 .timer 回答「什么时候去启动它」——每 10 分钟、每天凌晨 3 点、开机 5 分钟后 .service 是名词，.timer 是闹钟。闹钟响了就去按一下那个 service 的启动按钮，至于按下去之后发生什么，是 service 自己的事。\n所以常驻型的程序只要一个 .service（它自己不停，不需要闹钟），周期型的要一对，而且两个文件必须同名：myapp-sync.service 配 myapp-sync.timer——timer 靠文件名找到对应的 service，改错一个字母就是「闹钟响了但没有任何反应」。\n两种文件都放在 /etc/systemd/system/。\n二、常驻型：Type=simple + Restart=always 先看一个真实的例子。一个 Python 写的后台 worker，从队列里领任务、执行、再领下一个，永远不退出：\n[Unit] Description=myapp 后台 worker After=network-online.target docker.service Wants=network-online.target [Service] Type=simple Environment=TZ=Asia/Shanghai WorkingDirectory=/var/www/myapp ExecStart=/var/www/myapp/.venv/bin/python -m worker Restart=always RestartSec=10 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target 逐段拆开。\n[Unit]：启动顺序 字段 含义 不写会怎样 Description systemctl status 里显示的一句话 只显示文件名，排查时看不出是干什么的 After= 排在这些单元之后启动 可能在网络就绪前就启动，连不上数据库直接失败 Wants= 顺带把它也拉起来，但它失败不影响自己 — After 和 Wants 的区别是顺序和依赖：After 只管先后，不保证对方会被启动；Wants 管启动，但不管顺序。所以要「等网络好了再启动」，两个都要写。\n拿包管理器的依赖来类比 这套东西本质上就是服务之间的依赖声明，和 composer.json / package.json 里写依赖是同一件事：声明「我要用到谁」，由一个统一的东西去解析、排序、拉起来。\n对应关系大致是这样：\n包管理器 systemd 含义 require（必需依赖） Requires= 它起不来，我就不起 suggest（可选依赖） Wants= 顺带拉它一把，它失败不影响我 conflict Conflicts= 两个不能同时存在 （没有对应的） After= / Before= 纯粹的先后顺序 最后一行是关键：包管理器没有「顺序」这个维度。npm 装完就完事了，包躺在 node_modules 里，什么时候用是你的事。而服务是活的——「装了」和「此刻能用」是两回事，所以 systemd 把依赖拆成了两个正交的轴：\nRequires / Wants → 要不要把它拉起来 After / Before → 谁先谁后 这两个轴互不影响，这是最反直觉的地方。只写 Requires=docker.service 而不写 After=，systemd 会把 docker 拉起来，但可能和你的服务同时启动——docker 还在初始化，你的 docker exec 已经跑了，然后失败。所以实践中这两个几乎总是成对出现。\n另一个差别：包依赖是装的时候解析一次，之后就固定了；服务依赖是每次开机重新解析，而且服务跑着跑着还会挂掉——所以除了声明依赖，还得有 Restart= 这类运行期的兜底，这在包管理器里是没有对应概念的。\n注意 network-online.target 和 network.target 不是一回事：后者只表示「网络子系统起来了」，网卡可能还没拿到 IP。要真的能发请求，用前者。\nAfter= 里该写什么？先把机器上有哪些单元列出来 不用猜，也不用背。名字必须和机器上实际存在的单元完全一致，写错了不会报错，只是那一行静默失效。\n# 装了哪些单元（含从没启动过的）——找名字用这条 systemctl list-unit-files --type=service # 当前加载的单元和它们的状态，--all 连没在跑的也列出来 systemctl list-units --type=service --all # 名字记不全就用通配符 systemctl list-unit-files \u0026#34;docker*\u0026#34; systemctl list-unit-files \u0026#34;*sql*\u0026#34; # 所有 target（那些「集合点」） systemctl list-units --type=target # 看某个单元到底是怎么定义的（含它自己的 After/Wants） systemctl cat docker.service # 某个 target 由谁满足、拉起了哪些东西 systemctl list-dependencies network-online.target list-unit-files 和 list-units 的区别值得记一下：前者列「磁盘上装了哪些单元文件」，后者列「systemd 此刻加载了哪些」。找依赖名字用前者，因为你要依赖的服务这会儿可能没在跑。\n输出长这样，最后一列 STATE 是有没有设开机自启：\n$ systemctl list-unit-files \u0026#34;docker*\u0026#34; UNIT FILE STATE PRESET docker.service enabled enabled docker.socket enabled enabled 几个最常用的依赖对象：\n写在 After= 里 什么时候需要 network-online.target 要发网络请求 docker.service 命令是 docker exec、或程序连容器里的服务 mysql.service / mariadb.service / postgresql.service 直连本机数据库（名字按发行版不同，先列一下） redis-server.service 直连本机 Redis nginx.service 需要 Web 服务先在 remote-fs.target 数据在 NFS 之类的网络存储上 time-sync.target 逻辑依赖准确时间（对时前系统时间可能差很远） network-online.target 是 systemd 自带的，每台跑 systemd 的机器都有——\n$ systemctl cat network-online.target # /usr/lib/systemd/system/network-online.target # This file is part of systemd. 所以这个名字永远写得出来。有差别的只是它会不会真的等（取决于前面说的 wait-online 服务有没有 enable）。而数据库那类就不一定了：同一个 MySQL，Debian 上叫 mysql.service，CentOS 上可能是 mysqld.service，装了 MariaDB 又变成 mariadb.service——写之前一定先列一下。\n[Service]：怎么跑 Type= 决定 systemd 怎么判断「启动完成了」，这是最容易配错的一个字段：\n取值 含义 用在什么程序上 simple 命令一执行就算启动成功 常驻程序，本身不退出 oneshot 命令跑完退出才算成功 一次性任务，配 timer 用 forking 命令会 fork 出子进程后自己退出 老式的守护进程（nginx -d 那种） notify 程序自己通过 sd_notify 告诉 systemd 「我好了」 需要程序配合支持 写错的后果很直接：常驻程序写成 oneshot，systemd 会一直等它退出，systemctl start 挂住不返回；一次性任务写成 simple，命令还没跑完就被当成「已启动」，timer 会在下一轮准时再开一个。\nRestart= 决定进程没了要不要拉起来：\n取值 行为 no（默认） 退出就退出了，不管 on-failure 只有非 0 退出码或被信号杀死才重启；正常退出（exit 0）不管 always 不管怎么退出的都重启，包括正常退出 常驻 worker 用 always：它正常退出本身就是不该发生的事。而如果这个程序会正常结束，千万别用 always——它一退出就被拉起来，等于变成了死循环。\nRestartSec=10 是重启前等 10 秒。这个值不要设成 0：如果失败的原因是数据库连不上，0 秒重启就是每秒撞一次墙，日志几分钟刷几万行，真正的错误被淹掉。\nEnvironment=TZ=Asia/Shanghai 这一行的讲究 服务器时区通常是 UTC，不该为了一个程序去改整机时区（同一台机器上别的服务、数据库都在按 UTC 记时间）。Environment= 只给这一个单元设环境变量，影响面刚好。\n但设完之后会出现一个奇怪的现象：\nSep 02 06:18:52 my-server myapp[123]: [14:18:52] 开始处理任务 └──────┬───────┘ └────┬────┘ journald 打的前缀 程序自己打的时间 还是 UTC 已经是北京时间 同一行里两个时间差了 8 小时。因为前缀是 journald 加的，它按宿主机时区走，不受单元里的 TZ 影响。看日志时给 journalctl 也套上时区，两截才对齐：\nTZ=Asia/Shanghai journalctl -u myapp-worker -f [Install]：enable 时把链接建到哪 WantedBy=multi-user.target 是「开机进入多用户模式时启动我」。这一段只在 systemctl enable 时起作用——enable 就是照着它建一个符号链接。\n所以 start 和 enable 是两件独立的事：\nsystemctl start myapp-worker # 现在跑起来，但重启机器就没了 systemctl enable myapp-worker # 开机自启，但现在不动 systemctl enable --now myapp-worker # 两件事一起做 ← 通常要的是这个 没写 [Install] 段的单元不能被 enable（会报 The unit files have no installation config）。timer 触发的那种 service 就属于这一类，见下一节。\n三、周期型：Type=oneshot + .timer 同一个项目里另一个需求：每 10 分钟去拉一次订单数据。\nservice 部分（myapp-sync.service）：\n[Unit] Description=myapp 拉订单数据 After=network-online.target docker.service Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/bin/docker exec -u 1000 app-workspace-1 sh -c \u0026#34;cd /var/www/myapp \u0026amp;\u0026amp; php artisan orders:sync --concurrency=5\u0026#34; TimeoutStartSec=900 StandardOutput=journal StandardError=journal 注意这里没有 [Install] 段，也没有 Restart=。它不需要开机自启——负责自启的是 timer；它也不该被重启——一次性任务失败了就等下一轮，重试是 timer 的节奏说了算。\nTimeoutStartSec=900 是「跑超过 15 分钟就判失败、杀掉」。这一行对定时任务是必需的：某一轮卡在一个不返回的网络请求上，没有超时它会永远占着，后面每一轮都被跳过，而表面上看服务还是「正在运行」。\ntimer 部分（myapp-sync.timer）：\n[Unit] Description=每 10 分钟拉一次订单数据 [Timer] OnCalendar=*:0/10 RandomizedDelaySec=30 Persistent=true AccuracySec=10s [Install] WantedBy=timers.target 这次 [Install] 写在 timer 上——要 enable 的是闹钟，不是被叫醒的那个。\nOnCalendar 怎么写 语法是 星期 年-月-日 时:分:秒，前面的部分可以省略，* 是任意，/ 是步进：\n写法 含义 *:0/10 每 10 分钟（0、10、20、30、40、50 分） hourly 每小时整点 *-*-* 03:00:00 每天凌晨 3 点 Mon..Fri 09:00 周一到周五早 9 点 *-*-01 00:00:00 每月 1 号零点 写完一定要验一下，别等到第二天发现没跑：\n$ systemd-analyze calendar \u0026#34;*:0/10\u0026#34; Original form: *:0/10 Normalized form: *-*-* *:00/10:00 Next elapse: Wed 2026-09-02 06:30:00 UTC From now: 6min left 它会把你写的式子标准化，并算出下一次什么时候触发。写错了这里会直接报 Failed to parse calendar specification。\n另一种写法是 OnUnitActiveSec=10min，意思是「上一轮跑完之后再过 10 分钟」。区别是：OnCalendar 对齐到整点（10:00、10:10、10:20），OnUnitActiveSec 是相对的，任务跑了 3 分钟的话下一轮在 10:13。要固定节奏用前者，要保证间隔用后者。\n另外三个字段 RandomizedDelaySec=30：在触发时刻上加 0~30 秒的随机延迟。一台机器上如果有好几个整点任务，不错开的话它们会在同一秒一起启动，瞬间把 CPU 和网络打满。\nPersistent=true：机器关机期间错过的触发，开机后补跑一次。注意是一次，不是把错过的每一轮都补上——关机两天不会开机就打 288 次请求。\nAccuracySec=10s：允许的误差。systemd 默认是 1 分钟，它会在这个窗口里挑一个时刻把多个任务合并唤醒以省电。对服务器来说省电没意义，收紧到 10 秒让触发更准时。\ntimer 自带的一个好处：不会重叠 上一轮还在跑，这一轮不会被触发。 因为 timer 的动作是「启动那个 service」，而一个 service 已经处于 active 状态时，再启动它是空操作。\n这是 crontab 没有的。cron 到点就起一个新进程，不管上一个死没死——任务偶尔跑得比周期长，进程就会越堆越多，最后把机器拖垮。要在 cron 里避免这件事，得自己写文件锁或者上 flock。\n装上去 scp myapp-sync.service myapp-sync.timer root@server:/etc/systemd/system/ ssh root@server \u0026#39;systemctl daemon-reload \u0026amp;\u0026amp; systemctl enable --now myapp-sync.timer\u0026#39; 改完任何单元文件都必须 daemon-reload，否则 systemd 用的还是内存里的旧版本，你改的东西一点效果都没有——而且它不会提示你。\n四、为什么用 timer 不用 crontab crontab 一行就能写完，timer 要两个文件，看起来是倒退。但机器一多、任务一复杂，差距就出来了：\ncrontab systemd timer 上一轮没跑完 照样起新的，进程堆积 自动跳过 日志 自己 \u0026gt;\u0026gt; /var/log/xxx.log，还要自己轮转 进 journal，自动轮转、可按单元过滤 失败了怎么知道 默认发邮件（通常没配，等于没有） systemctl status 有退出码，可配 OnFailure= 触发告警 下次什么时候跑 自己按表达式推算 systemctl list-timers 直接告诉你 环境变量 极简的 PATH，经常「手动能跑定时跑不了」 显式写 Environment=，不依赖登录环境 超时控制 没有 TimeoutStartSec= 错过的补跑 没有 Persistent=true 依赖关系 没有 After= / Requires= 那条「手动能跑定时跑不了」是 cron 最经典的坑：cron 的 PATH 通常只有 /usr/bin:/bin，你在自己 shell 里配的一切它都没有。systemd 也是干净环境，但它逼你把命令写成绝对路径，问题在写配置时就暴露了，而不是在半夜。\n反过来说，crontab 也不是不能用——一台机器只有一两个简单任务，且没有 systemd（比如容器内部）时，cron 更省事。判断标准是这个任务失败了会不会有人管：会，就用 timer。\n五、命令实际跑在容器里：那个 -u 1000 现在很多部署是「代码在宿主机上，运行环境在容器里」——宿主机上根本没装 PHP/Python，命令要 docker exec 进容器跑。这时 ExecStart 长这样：\n/usr/bin/docker exec -u 1000 app-workspace-1 sh -c \u0026#34;cd /var/www/myapp \u0026amp;\u0026amp; php artisan orders:sync\u0026#34; └──────┬───────┘ └──┬──┘ └──────┬─────┘ └────────────┬────────────┘ 绝对路径 ★ 以哪个用户身份 ★ 容器名 真正要跑的命令 两个标 ★ 的地方都会让人白折腾半天。\n一、docker 必须写绝对路径 systemd 启动服务时的 PATH 不包含你登录 shell 里的那些目录。ExecStart=docker exec ... 会直接报：\nFailed to locate executable docker: No such file or directory which docker 查出来填进去。这条对所有命令都成立，不只是 docker。\n二、-u 1000：用错身份会留下一地属主不对的文件 docker exec 默认以容器里的 root 执行。而 Web 应用的进程（php-fpm、uwsgi）通常是以某个普通用户跑的（uid 1000）。两者写同一批目录，就会出事：\n# 不加 -u，root 跑出来的缓存文件： -rw-r--r-- 1 root root cache/config.php ← php-fpm 是 uid 1000，写不进去 而且它不会立刻炸。已经生成好的缓存文件人人可读，服务照常。等到某个请求需要新写一个缓存文件时才失败——看起来就像「部署完过一会儿突然 500」，排查时根本想不到是几小时前那条定时任务干的。\n所以规则很简单：定时任务用哪个身份跑，就要和 Web 进程用同一个身份。\n万一忘了，补救是把属主改回去：\ndocker exec app-workspace-1 chown -R 1000:1000 /var/www/myapp/storage /var/www/myapp/cache 三、更隐蔽的一种：以 1000 的身份，却读不到配置文件 上面那个坑还有个镜像版本。假设配置文件是这样：\n-rw------- 1 root root .env ← 权限 600，属主 root 以 uid 1000 的身份跑「生成配置缓存」这类命令时，它读不到这个文件——但很多框架不会因此报错，而是当成「没有配置文件」，用一套默认值生成了缓存。命令行里全是绿色的成功提示，实际上缓存里的数据库配置已经变成了默认的本地 SQLite，之后每个请求都去找一个不存在的文件。\n这个比上一个更难查，因为没有任何错误信息。防的办法是让配置文件的属主和跑命令的身份一致：\nchown 1000:1000 .env # 权限保持 600 不变 这不降低安全性——权限还是 600，只有属主能读，而 root 本来就能读任何文件。\n跑完之后自查一句，比什么都管用：\ngrep -c \u0026#34;sqlite\u0026#34; cache/config.php # 期望是 0，不是 0 就说明缓存被污染了 六、排查这几条就够 # 这个单元现在什么状态、最近几行日志 systemctl status myapp-sync.service # 所有定时器：上次什么时候跑的、下次什么时候 systemctl list-timers --all # 只看某个单元的日志，-f 跟踪，-n 50 看最后 50 行 TZ=Asia/Shanghai journalctl -u myapp-sync -n 50 # 只看这次开机以来的错误 journalctl -u myapp-sync -p err -b # 不等定时，立刻手动跑一轮（跑的是 service，不是 timer） systemctl start myapp-sync.service # 上一轮的退出码 systemctl show myapp-sync.service -p Result -p ExecMainStatus 最后一条尤其有用：Result=success / ExecMainStatus=0 才是真跑成功了。有些命令即使内部有失败，只要进程退出码是 0，systemd 就认为成功——所以脚本内部有失败时要返回非 0 退出码，否则 systemd 这一层永远看不到问题。\n小结 .service 说「怎么跑」，.timer 说「什么时候跑」，周期任务两个文件同名成对。 Type=simple 给常驻程序，Type=oneshot 给一次性任务，配反了要么 start 挂住，要么任务重叠。 Restart=always 只给「本不该退出」的程序，会正常结束的程序用它等于死循环；RestartSec 不要给 0。 timer 比 crontab 多给的东西：不重叠、进 journal、有退出码、能查下次触发时间、超时控制——任务失败了有人管，就用 timer。 OnCalendar 写完用 systemd-analyze calendar 验一遍；改完任何单元文件先 daemon-reload。 依赖和顺序是两个正交的轴：Requires/Wants 管「要不要拉起来」，After/Before 管「谁先谁后」，通常要成对写。 After= 里的名字先用 systemctl list-unit-files 列一下，写错不报错、只是静默失效；数据库那类的单元名各发行版不一样。 命令写绝对路径，systemd 的 PATH 很干净。 跑在容器里的话，身份要和 Web 进程一致（docker exec -u）——属主错了不会立刻炸，会在几小时后以「突然 500」的形式出现；配置文件读不到甚至完全不报错，只是缓存了一份默认值。 ","permalink":"https://www.yichengcode.com/posts/systemd-service-timer/","summary":"\u003cp\u003e一个后台程序要一直跑着，挂了自动拉起来；另一个程序要每 10 分钟跑一次，跑完就退出。这两件事在 Linux 上都归 systemd 管，前者写成 \u003ccode\u003e.service\u003c/code\u003e，后者要 \u003ccode\u003e.service\u003c/code\u003e + \u003ccode\u003e.timer\u003c/code\u003e 一对。\u003c/p\u003e\n\u003cp\u003e本文把这两种单元的每个字段拆开讲清楚，说明为什么定时任务用 timer 比 crontab 更合适，以及命令实际跑在 Docker 容器里时最容易踩的那个坑。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"一先分清楚-service-和-timer\"\u003e一、先分清楚 service 和 timer\u003c/h2\u003e\n\u003cp\u003e很多人第一次配 timer 时会困惑：「定时任务为什么要写两个文件？」\u003c/p\u003e\n\u003cp\u003e因为这是两件事：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e\u003ccode\u003e.service\u003c/code\u003e 回答「这个程序该怎么跑」\u003c/strong\u003e——跑什么命令、用哪个用户、挂了要不要重启、超时多久算失败\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e\u003ccode\u003e.timer\u003c/code\u003e 回答「什么时候去启动它」\u003c/strong\u003e——每 10 分钟、每天凌晨 3 点、开机 5 分钟后\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003ccode\u003e.service\u003c/code\u003e 是名词，\u003ccode\u003e.timer\u003c/code\u003e 是闹钟。闹钟响了就去按一下那个 service 的启动按钮，至于按下去之后发生什么，是 service 自己的事。\u003c/p\u003e\n\u003cp\u003e所以\u003cstrong\u003e常驻型的程序只要一个 \u003ccode\u003e.service\u003c/code\u003e\u003c/strong\u003e（它自己不停，不需要闹钟），\u003cstrong\u003e周期型的要一对\u003c/strong\u003e，而且两个文件必须\u003cstrong\u003e同名\u003c/strong\u003e：\u003ccode\u003emyapp-sync.service\u003c/code\u003e 配 \u003ccode\u003emyapp-sync.timer\u003c/code\u003e——timer 靠文件名找到对应的 service，改错一个字母就是「闹钟响了但没有任何反应」。\u003c/p\u003e\n\u003cp\u003e两种文件都放在 \u003ccode\u003e/etc/systemd/system/\u003c/code\u003e。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"二常驻型typesimple--restartalways\"\u003e二、常驻型：\u003ccode\u003eType=simple\u003c/code\u003e + \u003ccode\u003eRestart=always\u003c/code\u003e\u003c/h2\u003e\n\u003cp\u003e先看一个真实的例子。一个 Python 写的后台 worker，从队列里领任务、执行、再领下一个，永远不退出：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-ini\" data-lang=\"ini\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003e[Unit]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eDescription\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003emyapp 后台 worker\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eAfter\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003enetwork-online.target docker.service\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eWants\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003enetwork-online.target\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003e[Service]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eType\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003esimple\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eEnvironment\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eTZ=Asia/Shanghai\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eWorkingDirectory\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/var/www/myapp\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eExecStart\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/var/www/myapp/.venv/bin/python -m worker\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eRestart\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003ealways\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eRestartSec\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eStandardOutput\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003ejournal\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eStandardError\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003ejournal\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003e[Install]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eWantedBy\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003emulti-user.target\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e逐段拆开。\u003c/p\u003e","title":"systemd 单元配置实战：常驻服务、定时任务，以及跑在容器里的命令"},{"content":"数据库端口不想对公网开，但本地又要用图形工具连；内网的服务想临时给外面看一眼；出差时想让所有流量走回自己的服务器——这几件事都不用装额外软件，SSH 自带的端口转发就能做。\n本文讲清楚三种转发方式的区别、参数怎么记、以及那些一看就懂但一用就错的地方。\n一、先理解一件事：转发的是「连接」，不是「端口」 很多教程一上来就给命令，看完照抄能跑，但换个场景就不会改了。原因是没想清楚数据往哪边流。\nSSH 转发的本质是：在一台机器上开个监听端口，谁连上来，就把这条连接通过已建立的 SSH 加密通道，转到另一端去替他连目标地址。\n所以每次写命令，只需要回答三个问题：\n在哪台机器上监听？（决定用 -L 还是 -R） 监听哪个端口？ 连接最终要送到哪个地址、哪个端口？——从哪一端的视角看？ 第三个问题是所有困惑的来源，后面会反复强调。\n二、本地转发 -L：把远端的服务搬到本地 这是最常用的一种。典型场景：数据库只监听 127.0.0.1，不对公网开放，但你要用本地的图形工具连它。\nssh -L 13306:127.0.0.1:3306 root@db-server 参数怎么读 -L 13306:127.0.0.1:3306 └─┬─┘ └────┬────┘ └┬─┘ │ │ └─ 目标端口 │ └─ 目标地址（★ 从 db-server 的视角看 ★） └─ 在你本机监听的端口 中间那个地址是站在服务器上看的，这是最容易搞错的地方。127.0.0.1 在这里指的不是你的电脑，而是 db-server 自己。\n数据怎么走 你的电脑 db-server mysql -h 127.0.0.1 -P 13306 │ └─→ 本机 13306（ssh 在监听） │ │ ═══ SSH 加密隧道（走 22 端口）═══ │ └─→ 127.0.0.1:3306（MySQL） 关键在于：MySQL 看到的连接来自它自己的 127.0.0.1，因为最后一跳是 db-server 内部发起的。所以数据库完全不用对外开放端口，防火墙只需要放行 22。\n用法 # 终端 1：开隧道 ssh -L 13306:127.0.0.1:3306 root@db-server # 终端 2：在本机连，就像连本地库一样 mysql -h 127.0.0.1 -P 13306 -u app_user -p 为什么本地端口用 13306 而不是 3306 如果你本机也装了 MySQL，3306 已被占用，隧道会起不来（bind: Address already in use）。换个不冲突的端口最省事，13306、23306 都行。\n目标地址不一定是 127.0.0.1 如果数据库在另一台内网机器上，SSH 的这台只是跳板：\nssh -L 13306:10.0.0.20:3306 root@jump-server # └─ 内网数据库地址，从 jump-server 的视角能访问到就行 这就是「堡垒机」的典型用法：只有跳板机能 SSH，数据库藏在内网，你从家里照样能连。\n三、远程转发 -R：把本地的服务送到远端 方向反过来。典型场景：你本地跑着一个开发中的服务，想让服务器（或通过服务器让别人）访问它。\nssh -R 8080:127.0.0.1:3000 root@remote-server 参数怎么读 -R 8080:127.0.0.1:3000 └─┬┘ └────┬────┘ └┬─┘ │ │ └─ 目标端口 │ └─ 目标地址（★ 这次是从你本机的视角看 ★） └─ 在 remote-server 上监听的端口 注意视角又变了：-R 的时候，监听端在远端，目标地址从本机视角看。\n数据怎么走 你的电脑 remote-server 本地 3000（你的开发服务） 别人 curl localhost:8080 ↑ │ └─ 127.0.0.1:3000 ←────────────────────────┘ ═══ SSH 加密隧道 ═══ 一个坑：默认只有服务器自己能访问 -R 开出来的端口默认只绑在服务器的 127.0.0.1 上，也就是说只有登录到那台服务器的人能用，外网访问不到。\n想让外网也能访问，需要两步：\n# 1. 服务器的 /etc/ssh/sshd_config 里加上 GatewayPorts yes # 然后 systemctl restart sshd # 2. 客户端明确绑到 0.0.0.0 ssh -R 0.0.0.0:8080:127.0.0.1:3000 root@remote-server 开 GatewayPorts 前想清楚：这等于允许任何能 SSH 进来的人，把服务器变成一个对外的入口。多人共用的机器上要谨慎。\n四、动态转发 -D：把 SSH 变成 SOCKS 代理 ssh -D 1080 root@remote-server 这个不指定目标地址——它在本地开一个 SOCKS5 代理，任何支持 SOCKS 的程序把流量交给它，都会从服务器那端出去。\n# 用它访问 curl --socks5 127.0.0.1:1080 https://example.com # git 也支持 git config --global http.proxy socks5://127.0.0.1:1080 浏览器配置 SOCKS5 代理 127.0.0.1:1080 之后，所有网页请求都从服务器出口走。\n和 -L 的区别：-L 是一个端口对一个目标，-D 是一个端口对任意目标，由客户端在协议里告诉代理要连谁。\n五、常用参数组合 -N：不要远程终端 直接 ssh -L ... 会在开隧道的同时给你一个远程 shell。如果只想要隧道：\nssh -N -L 13306:127.0.0.1:3306 root@db-server -N 的意思是「不执行远程命令」，于是它就挂在那儿只做转发，不给 shell。\n-f：丢到后台 ssh -fN -L 13306:127.0.0.1:3306 root@db-server 连终端都不占。关闭的时候按命令特征杀掉：\npkill -f \u0026#34;13306:127.0.0.1:3306\u0026#34; 多条隧道一次开 -L 可以重复写：\nssh -fN \\ -L 13306:127.0.0.1:3306 \\ -L 16379:127.0.0.1:6379 \\ -L 27017:127.0.0.1:27017 \\ root@db-server 一条 SSH 连接同时转发 MySQL、Redis、MongoDB。\n让隧道别断 长时间不用会被中间的防火墙或 NAT 掐掉。加心跳：\nssh -fN -o ServerAliveInterval=30 -o ServerAliveCountMax=3 \\ -L 13306:127.0.0.1:3306 root@db-server 每 30 秒发一次心跳，连续 3 次没响应才判定断开。\n写进 ssh config，一劳永逸 ~/.ssh/config：\nHost db-tunnel HostName db-server.example.com User root LocalForward 13306 127.0.0.1:3306 LocalForward 16379 127.0.0.1:6379 ServerAliveInterval 30 ExitOnForwardFailure yes 以后只要：\nssh -fN db-tunnel ExitOnForwardFailure yes 很有用——端口被占用时直接失败退出，而不是「SSH 连上了但转发没成」这种最难排查的半吊子状态。\n六、几个高频踩坑 1. 连的时候写了 localhost mysql -h localhost -P 13306 # 连不上 mysql -h 127.0.0.1 -P 13306 # 正确 MySQL 客户端对 localhost 会走 unix socket 文件，根本不走 TCP，自然也就绕过了隧道。必须写 127.0.0.1 强制走 TCP。\n同理，-P（大写）是端口，-p（小写）是密码，写错了会得到莫名其妙的报错。\n2. 搞反了中间那个地址的视角 再强调一次：\n监听在哪 中间的目标地址从谁的视角看 -L 本地 远端服务器 -R 远端服务器 本地 记忆方法：目标地址永远是「隧道出口那一端」要去连的地址。-L 的出口在服务器，-R 的出口在本地。\n3. 以为隧道断了，其实是端口没释放 隧道进程被 kill 之后偶尔端口还占着，再开会报 Address already in use。查一下谁在占：\nlsof -i :13306 4. 服务器禁用了转发 有些机器出于安全考虑在 sshd_config 里关掉了转发：\nAllowTcpForwarding no 这时候 -L 会静默失败或者报 administratively prohibited。需要服务器管理员放开。\n七、什么时候该用隧道，什么时候该用别的 隧道好用，但不是所有场景的最优解。\n适合用隧道：\n临时的、交互式的访问——比如用图形工具连生产库查个数据 开发调试时访问内网服务 不想为了一次性的需求去改防火墙规则 不适合用隧道：\n常驻服务之间的连接。比如备份机上的定时任务要每天连数据库，靠一条挂着的 SSH 隧道很脆弱——断了没人知道，任务就静默失败了。这种场景更适合用防火墙规则放行特定源 IP：\nufw allow from 10.0.0.5 to any port 3306 高吞吐的数据传输。所有流量都经过 SSH 加解密，大批量导数据时会成为瓶颈。\n一个实用的判断标准：这条连接是「人在用」还是「机器在用」？ 人用的走隧道，机器长期用的走防火墙白名单。\n小结 -L 本地转发：把远端服务搬到本地，最常用，中间的地址从服务器视角看 -R 远程转发：把本地服务送到远端，中间的地址从本地视角看，默认只有服务器自己能访问 -D 动态转发：变成 SOCKS5 代理，一个端口通向任意目标 -fN 是日常最实用的组合：后台运行、不要 shell 写进 ~/.ssh/config 加上 ExitOnForwardFailure yes，比每次敲长命令可靠 数据库、Redis 这类服务的正确姿势是：端口只绑 127.0.0.1，人要用就开隧道，机器要用就配防火墙白名单。关于数据库账号本身该怎么分离、授权和限制来源，可以接着看 MySQL 用户最佳实践：按站分离、改密码、改授权、限制 IP。\n","permalink":"https://www.yichengcode.com/posts/ssh-tunnel/","summary":"\u003cp\u003e数据库端口不想对公网开，但本地又要用图形工具连；内网的服务想临时给外面看一眼；出差时想让所有流量走回自己的服务器——这几件事都不用装额外软件，SSH 自带的端口转发就能做。\u003c/p\u003e\n\u003cp\u003e本文讲清楚三种转发方式的区别、参数怎么记、以及那些一看就懂但一用就错的地方。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"一先理解一件事转发的是连接不是端口\"\u003e一、先理解一件事：转发的是「连接」，不是「端口」\u003c/h2\u003e\n\u003cp\u003e很多教程一上来就给命令，看完照抄能跑，但换个场景就不会改了。原因是没想清楚数据往哪边流。\u003c/p\u003e\n\u003cp\u003eSSH 转发的本质是：\u003cstrong\u003e在一台机器上开个监听端口，谁连上来，就把这条连接通过已建立的 SSH 加密通道，转到另一端去替他连目标地址。\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e所以每次写命令，只需要回答三个问题：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e在哪台机器上监听？\u003c/strong\u003e（决定用 \u003ccode\u003e-L\u003c/code\u003e 还是 \u003ccode\u003e-R\u003c/code\u003e）\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e监听哪个端口？\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e连接最终要送到哪个地址、哪个端口？——从哪一端的视角看？\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e第三个问题是所有困惑的来源，后面会反复强调。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"二本地转发--l把远端的服务搬到本地\"\u003e二、本地转发 \u003ccode\u003e-L\u003c/code\u003e：把远端的服务搬到本地\u003c/h2\u003e\n\u003cp\u003e这是最常用的一种。典型场景：\u003cstrong\u003e数据库只监听 \u003ccode\u003e127.0.0.1\u003c/code\u003e，不对公网开放，但你要用本地的图形工具连它。\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003essh -L 13306:127.0.0.1:3306 root@db-server\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"参数怎么读\"\u003e参数怎么读\u003c/h3\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e-L 13306:127.0.0.1:3306\n   └─┬─┘ └────┬────┘ └┬─┘\n     │        │       └─ 目标端口\n     │        └─ 目标地址（★ 从 db-server 的视角看 ★）\n     └─ 在你本机监听的端口\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003cstrong\u003e中间那个地址是站在服务器上看的\u003c/strong\u003e，这是最容易搞错的地方。\u003ccode\u003e127.0.0.1\u003c/code\u003e 在这里指的不是你的电脑，而是 db-server 自己。\u003c/p\u003e\n\u003ch3 id=\"数据怎么走\"\u003e数据怎么走\u003c/h3\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e你的电脑                                  db-server\n                                          \nmysql -h 127.0.0.1 -P 13306\n      │\n      └─→ 本机 13306（ssh 在监听）\n                 │\n                 │ ═══ SSH 加密隧道（走 22 端口）═══\n                                        │\n                                        └─→ 127.0.0.1:3306（MySQL）\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e关键在于：\u003cstrong\u003eMySQL 看到的连接来自它自己的 \u003ccode\u003e127.0.0.1\u003c/code\u003e\u003c/strong\u003e，因为最后一跳是 db-server 内部发起的。所以数据库完全不用对外开放端口，防火墙只需要放行 22。\u003c/p\u003e","title":"SSH 隧道详解：本地转发、远程转发与动态转发"},{"content":"一台服务器上跑着好几个站，图省事全用 root 连库——这是最常见的做法，也是出事时代价最大的做法。本文讲怎么把数据库账号按站拆开、怎么安全地改密码和授权、怎么把账号锁到指定来源 IP，以及几个容易被忽略的配套问题。\n文中场景与配置均已脱敏。\n一、为什么要按站分离 假设一台机器上跑两个站：一个商城前台，一个用 WordPress 做的收单页。两个站各有各的库。\n如果两个站都用 root 连库，那么任何一个站被打穿，攻击者拿到的就是整台机器上所有库的完全控制权——包括另一个站的订单、用户、支付记录。一个不起眼的插件漏洞，赔上的是全部数据。\n正确做法是一站一账号，每个账号只授权自己那个库：\nCREATE USER \u0026#39;shop_app\u0026#39;@\u0026#39;%\u0026#39; IDENTIFIED BY \u0026#39;强密码\u0026#39;; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON `shop`.* TO \u0026#39;shop_app\u0026#39;@\u0026#39;%\u0026#39;; CREATE USER \u0026#39;wp_app\u0026#39;@\u0026#39;%\u0026#39; IDENTIFIED BY \u0026#39;另一个强密码\u0026#39;; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `wordpress`.* TO \u0026#39;wp_app\u0026#39;@\u0026#39;%\u0026#39;; FLUSH PRIVILEGES; 这样 WordPress 那个站即使被完全控制，攻击者在数据库层面也只能看到 wordpress 库，商城的数据碰不到。\n权限给到什么程度 Web 应用需要的其实就两类：\n数据操作：SELECT INSERT UPDATE DELETE——日常读写 结构操作：CREATE DROP INDEX ALTER——迁移（migration）要用 CREATE TEMPORARY TABLES 和 LOCK TABLES 按框架需要给。剩下的一律不给，尤其这几个：\n权限 作用 为什么不给 EVENT 数据库定时任务 应用调度一般在系统层（cron / systemd timer），用不上；给了等于让攻击者能植入定时后门 TRIGGER 触发器 业务逻辑写在应用里的项目用不到；给了能悄悄篡改写入的数据 EXECUTE 存储过程 同上 FILE 读写服务器文件 绝对不能给，能直接读 /etc/passwd、写 webshell GRANT OPTION 给别人授权 给了等于账号能自我提权 二、账号分离不等于安全 账号按上面的方式拆开之后，看起来隔离已经做到位了。但如果 WordPress 那个站被一个免登录 RCE 漏洞打穿，情况未必如预期。\n关键在于一个容易被忽略的细节：两个站共用同一个 php-fpm 进程池。攻击者拿到 WordPress 的执行权限后，进程身份和商城站是同一个，于是：\ncat /var/www/shop/.env # 直接读到了商城的数据库账号密码 数据库账号分离，在这种情况下形同虚设。 拦住的是「通过 SQL 越权访问」，没拦住的是「直接读另一个站的配置文件」。\n这件事的教训是：\n账号分离的前提，是被攻破的那个站拿不到另一个站的凭据。\n配套要做的至少有：\nphp-fpm 进程池按站分开，每个池跑在不同的系统用户下 配置文件权限收紧，.env 之类设成 640，属主是各自站点的用户 open_basedir 限制，让每个站的 PHP 只能访问自己的目录 只做数据库层面的分离，是把门锁了但窗户开着。\n三、改密码 标准做法 MySQL 5.7.6 及以后统一用 ALTER USER：\nALTER USER \u0026#39;shop_app\u0026#39;@\u0026#39;%\u0026#39; IDENTIFIED BY \u0026#39;新密码\u0026#39;; FLUSH PRIVILEGES; 老教程里的 SET PASSWORD = PASSWORD('...') 和直接 UPDATE mysql.user 都不要再用了，前者已废弃，后者会绕过密码加密逻辑把账号改坏。\n关键：先数清楚有几个地方用了这个账号 改密码本身一条命令，难的是同步。一个账号往往不止一处在用：\n应用的配置文件（.env、config.php） 后台脚本 / 定时任务的独立配置 别的机器上的脚本（备份机、数据分析机远程连过来） 连接池、中间件、监控探针 漏掉任何一个，那个服务就在下次连库时静默失败。而「静默」是最麻烦的——备份脚本连不上库不会有人立刻发现。\n稳妥的做法是改之前先全局搜一遍：\n# 在所有相关机器上搜账号名 grep -rn \u0026#34;shop_app\u0026#34; /var/www /etc /root/scripts 2\u0026gt;/dev/null 让脚本自己去读配置，而不是把密码抄进脚本 这是个很值回票价的习惯。比如备份脚本，不要写死密码：\n# 不好：密码写死在脚本里，改密码要改两个地方，还容易漏 mysqldump -u shop_app -p\u0026#39;密码写死在这\u0026#39; shop | gzip \u0026gt; backup.sql.gz 而是每次运行时从应用的配置文件里现读：\n# 好：单一事实来源，改了 .env 备份自动跟上 env_file=/var/www/shop/.env user=$(grep -m1 \u0026#39;^DB_USERNAME=\u0026#39; \u0026#34;$env_file\u0026#34; | cut -d= -f2- | tr -d \u0026#39;\u0026#34;\u0026#39;\u0026#34;\u0026#39;\u0026#34;\u0026#39; \u0026#39;) pass=$(grep -m1 \u0026#39;^DB_PASSWORD=\u0026#39; \u0026#34;$env_file\u0026#34; | cut -d= -f2- | tr -d \u0026#39;\u0026#34;\u0026#39;\u0026#34;\u0026#39;\u0026#34;\u0026#39; \u0026#39;) MYSQL_PWD=\u0026#34;$pass\u0026#34; mysqldump -u\u0026#34;$user\u0026#34; shop | gzip \u0026gt; backup.sql.gz 这么写之后，改密码只需要改 .env 一处，所有周边脚本自动跟上，不必逐个同步。\n密码不要出现在命令行里 mysql -u shop_app -p\u0026#39;密码\u0026#39; # 危险 同机器上任何用户 ps aux 都能看到这个密码，它还会进 shell 历史。用环境变量代替：\nMYSQL_PWD=\u0026#39;密码\u0026#39; mysql -u shop_app 或者写进 ~/.my.cnf 并 chmod 600。\n四、改授权 查看现有授权 改之前先看清楚现在有什么：\nSHOW GRANTS FOR \u0026#39;shop_app\u0026#39;@\u0026#39;%\u0026#39;; 输出大概是这样：\nGRANT USAGE ON *.* TO `shop_app`@`%` GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON `shop`.* TO `shop_app`@`%` 第一行的 USAGE ON *.* 意思是「这个账号存在，但在全局层面没有任何权限」——这是正常的，不代表有问题。\n收回权限 REVOKE EVENT, TRIGGER ON `shop`.* FROM \u0026#39;shop_app\u0026#39;@\u0026#39;%\u0026#39;; FLUSH PRIVILEGES; 收窄权限的代价：一个静默失败的备份 这是一个很典型的场景。\n假设把应用账号的权限收窄，去掉 EVENT、TRIGGER、EXECUTE——理由很充分：这套系统的定时任务在 systemd 层，业务逻辑全在应用代码里，数据库里根本没有事件、触发器、存储过程。\n结果第二天，每日备份失败了：\nmysqldump: Couldn\u0026#39;t execute \u0026#39;show events\u0026#39;: Access denied for user \u0026#39;shop_app\u0026#39;@\u0026#39;%\u0026#39; to database \u0026#39;shop\u0026#39; (1044) 原因是备份脚本用的是 mysqldump --routines --triggers --events——这几乎是所有教程里的标准写法，用来把存储过程、触发器、定时事件一并导出。而 --events 会先执行 SHOW EVENTS 探测一下，这一步被拒了，整个 dump 退出码非零，备份判定失败。\n最讽刺的地方是：库里根本一个 event 都没有。 为了导出一个不存在的东西，因为没权限探测，整份备份失败了。而且它是在最后一步失败的——数据已经导了 36 分钟，全部白费。\n这里有两种修法，选哪种取决于你怎么看：\n方案 做法 评价 把权限加回去 GRANT EVENT ON shop.* TO ... 为了一个用不到的功能，给长期在线的应用账号留一个能植入定时后门的权限。不划算 改备份命令 去掉 --events（连 --routines --triggers 一起去掉） 库里本来就没这些对象，导出内容完全一样。推荐 # 改后 mysqldump --single-transaction --quick --no-tablespaces \\ --databases shop | gzip \u0026gt; backup.sql.gz 这件事的普遍教训：收窄权限之后，要把所有用这个账号的运维工具跑一遍。权限问题不会在收窄的当下暴露，而是等到某个定时任务下次运行时才炸，中间可能已经过去好几天。\n这类失败还牵出另一个问题——没人盯着的自动备份等于没有备份。定时任务失败往往要过好几天才被发现，因为没有失败告警。备份脚本至少要做到：失败时发个通知，或者记录一个 .last_success 时间戳文件供巡检。\n五、限制 IP 'user'@'host' 里的 host 是账号的一部分 这是最容易搞错的地方：MySQL 的账号是 用户名 + 来源 两部分共同构成的。'shop_app'@'%' 和 'shop_app'@'10.0.0.5' 是两个完全不同的账号，各自有各自的密码和授权。\n所以「限制 IP」不是「修改一个属性」，而是建一个新账号、把授权重做一遍、再删掉旧的：\n-- 1. 建限定来源的新账号 CREATE USER \u0026#39;shop_app\u0026#39;@\u0026#39;10.0.0.5\u0026#39; IDENTIFIED BY \u0026#39;密码\u0026#39;; -- 2. 授权要重新给一遍，不会从旧账号继承 GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON `shop`.* TO \u0026#39;shop_app\u0026#39;@\u0026#39;10.0.0.5\u0026#39;; -- 3. 确认新账号能连、应用跑正常之后，再删旧的 DROP USER \u0026#39;shop_app\u0026#39;@\u0026#39;%\u0026#39;; FLUSH PRIVILEGES; 顺序很重要：先建新的、验证通过，再删旧的。反过来做会有一段时间应用连不上库。\nhost 的几种写法 写法 含义 'user'@'localhost' 只能通过 unix socket 本机连接 'user'@'127.0.0.1' 只能通过 TCP 本机连接（和上面不是一回事） 'user'@'10.0.0.5' 只有这个 IP 能连 'user'@'10.0.0.%' 整个网段能连 'user'@'%' 任何地方都能连——公网暴露时非常危险 localhost 和 127.0.0.1 的区别经常坑人：MySQL 客户端默认对 localhost 走 socket，对 127.0.0.1 走 TCP，两者匹配的是不同的账号记录。只建了 localhost 的账号，用 -h 127.0.0.1 连会报 access denied。\nDocker 场景要注意 如果 MySQL 跑在容器里、应用也在容器里，那么应用连过来的源 IP 是容器网络的 IP（比如 172.18.0.x），不是宿主机 IP。这时候可以按容器网段授权：\nCREATE USER \u0026#39;shop_app\u0026#39;@\u0026#39;172.18.%\u0026#39; IDENTIFIED BY \u0026#39;密码\u0026#39;; 但容器 IP 是会变的，更稳妥的做法是不依赖 IP，而是在网络层就不让外面连进来——见下一节。\n比账号限制更彻底的：端口别对外开 不少机器的 MySQL 直接监听 0.0.0.0:3306，全公网可连，只靠密码挡着。这意味着：\n任何人都能对你的库发起密码爆破 MySQL 本身的漏洞直接暴露在公网 用 docker port 或 ss -tlnp 检查一下：\nss -tlnp | grep 3306 # 危险：0.0.0.0:3306 # 安全：127.0.0.1:3306 Docker Compose 里端口映射写法的区别：\nports: - \u0026#34;3306:3306\u0026#34; # 危险：绑到 0.0.0.0，全公网可连 - \u0026#34;127.0.0.1:3306:3306\u0026#34; # 安全：只有宿主机能连 确实需要远程连（比如备份机在另一台机器上），正确做法是：\n端口只绑内网地址，或者干脆不映射 远程访问走 SSH 隧道，或者用防火墙只放行特定源 IP # 防火墙只放行备份机 ufw allow from 10.0.0.5 to any port 3306 # 或者走 SSH 隧道，数据库端口完全不对外 ssh -fN -L 13306:127.0.0.1:3306 user@db-server mysql -h 127.0.0.1 -P 13306 -u shop_app -p 隧道的三种转发方式、参数怎么记、以及「中间那个地址到底从谁的视角看」这类高频坑， 展开讲在这篇：SSH 隧道详解：本地转发、远程转发与动态转发。\n要提醒的是：隧道适合人用，不适合机器长期用。备份机上的定时任务靠一条挂着的隧道 很脆弱——断了没人知道，任务就静默失败。那种场景还是用上面的防火墙白名单更稳。\n六、一份可执行的检查清单 给现有系统做体检，按这个顺序过一遍：\n账号层面\n每个站是不是有自己的库账号，没有共用 root SELECT user, host FROM mysql.user; 看看有没有遗留的、不知道谁在用的账号 有没有账号是 @'%' 且授权了 *.* 有没有账号带 FILE、GRANT OPTION、SUPER SELECT user, host FROM mysql.user; SHOW GRANTS FOR \u0026#39;some_user\u0026#39;@\u0026#39;%\u0026#39;; 网络层面\nss -tlnp | grep 3306 是不是绑在 0.0.0.0 如果必须远程访问，是不是有防火墙规则限制源 IP 配套层面（最容易被忽略）\n各站的 php-fpm 池是不是分开的，跑在不同系统用户下 .env 之类的配置文件权限是不是 640，属主正确 一个站被打穿，能不能读到另一个站的配置文件（实际试一下） 运维层面\n备份脚本是从配置文件读凭据，还是把密码写死了 收窄权限之后，所有定时任务都手动跑过一遍了吗 备份失败有没有告警，或者有没有一个可巡检的成功时间戳 小结 数据库账号这件事，容易的部分是那几条 SQL，难的部分全在周边：\n分离账号只是第一步，配置文件读得到就等于没分离 改密码的成本在同步，让脚本从单一配置源读凭据能省掉大部分麻烦 收窄权限会打断运维工具，而且是延迟几天才炸，收窄后要主动把定时任务跑一遍 限制 IP 是换账号不是改属性，先建后删，顺序错了就是停机 最有效的一招其实是别把 3306 对外开，比任何账号策略都管用 ","permalink":"https://www.yichengcode.com/posts/mysql-user-best-practice/","summary":"\u003cp\u003e一台服务器上跑着好几个站，图省事全用 \u003ccode\u003eroot\u003c/code\u003e 连库——这是最常见的做法，也是出事时代价最大的做法。本文讲怎么把数据库账号按站拆开、怎么安全地改密码和授权、怎么把账号锁到指定来源 IP，以及几个容易被忽略的配套问题。\u003c/p\u003e\n\u003cp\u003e文中场景与配置均已脱敏。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"一为什么要按站分离\"\u003e一、为什么要按站分离\u003c/h2\u003e\n\u003cp\u003e假设一台机器上跑两个站：一个商城前台，一个用 WordPress 做的收单页。两个站各有各的库。\u003c/p\u003e\n\u003cp\u003e如果两个站都用 \u003ccode\u003eroot\u003c/code\u003e 连库，那么\u003cstrong\u003e任何一个站被打穿，攻击者拿到的就是整台机器上所有库的完全控制权\u003c/strong\u003e——包括另一个站的订单、用户、支付记录。一个不起眼的插件漏洞，赔上的是全部数据。\u003c/p\u003e\n\u003cp\u003e正确做法是一站一账号，每个账号只授权自己那个库：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eCREATE\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUSER\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;shop_app\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;%\u0026#39;\u003c/span\u003e IDENTIFIED \u003cspan style=\"color:#66d9ef\"\u003eBY\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;强密码\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eGRANT\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSELECT\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eINSERT\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUPDATE\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eDELETE\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eCREATE\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eDROP\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eINDEX\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eALTER\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eCREATE\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTEMPORARY\u003c/span\u003e TABLES, \u003cspan style=\"color:#66d9ef\"\u003eLOCK\u003c/span\u003e TABLES\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eON\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003eshop\u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003e.\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTO\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;shop_app\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;%\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eCREATE\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUSER\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;wp_app\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;%\u0026#39;\u003c/span\u003e IDENTIFIED \u003cspan style=\"color:#66d9ef\"\u003eBY\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;另一个强密码\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eGRANT\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSELECT\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eINSERT\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUPDATE\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eDELETE\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eCREATE\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eDROP\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eINDEX\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eALTER\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eON\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003ewordpress\u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003e.\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTO\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;wp_app\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;%\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eFLUSH \u003cspan style=\"color:#66d9ef\"\u003ePRIVILEGES\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这样 WordPress 那个站即使被完全控制，攻击者在数据库层面也只能看到 \u003ccode\u003ewordpress\u003c/code\u003e 库，商城的数据碰不到。\u003c/p\u003e\n\u003ch3 id=\"权限给到什么程度\"\u003e权限给到什么程度\u003c/h3\u003e\n\u003cp\u003eWeb 应用需要的其实就两类：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e数据操作\u003c/strong\u003e：\u003ccode\u003eSELECT INSERT UPDATE DELETE\u003c/code\u003e——日常读写\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e结构操作\u003c/strong\u003e：\u003ccode\u003eCREATE DROP INDEX ALTER\u003c/code\u003e——迁移（migration）要用\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003ccode\u003eCREATE TEMPORARY TABLES\u003c/code\u003e 和 \u003ccode\u003eLOCK TABLES\u003c/code\u003e 按框架需要给。剩下的一律不给，尤其这几个：\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e权限\u003c/th\u003e\n          \u003cth\u003e作用\u003c/th\u003e\n          \u003cth\u003e为什么不给\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eEVENT\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e数据库定时任务\u003c/td\u003e\n          \u003ctd\u003e应用调度一般在系统层（cron / systemd timer），用不上；给了等于让攻击者能植入定时后门\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eTRIGGER\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e触发器\u003c/td\u003e\n          \u003ctd\u003e业务逻辑写在应用里的项目用不到；给了能悄悄篡改写入的数据\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eEXECUTE\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e存储过程\u003c/td\u003e\n          \u003ctd\u003e同上\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eFILE\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e读写服务器文件\u003c/td\u003e\n          \u003ctd\u003e\u003cstrong\u003e绝对不能给\u003c/strong\u003e，能直接读 \u003ccode\u003e/etc/passwd\u003c/code\u003e、写 webshell\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eGRANT OPTION\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e给别人授权\u003c/td\u003e\n          \u003ctd\u003e给了等于账号能自我提权\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003chr\u003e\n\u003ch2 id=\"二账号分离不等于安全\"\u003e二、账号分离不等于安全\u003c/h2\u003e\n\u003cp\u003e账号按上面的方式拆开之后，看起来隔离已经做到位了。但如果 WordPress 那个站被一个免登录 RCE 漏洞打穿，情况未必如预期。\u003c/p\u003e","title":"MySQL 用户最佳实践：按站分离、改密码、改授权、限制 IP"},{"content":"在 Linux 工业级运维和日常高效开发中，终端（Terminal）是我们交互最频繁的工具。默认的 Bash 虽然稳定，但在自动补全、语法高亮和历史命令检索等日常痛点上显得力不从心。\n通过升级至 Zsh，配合 Oh My Zsh 生态以及核心的双核插件（zsh-autosuggestions 与 zsh-syntax-highlighting），你将在光标后获得灰色的历史命令暗影提示，只需按右方向键或 Ctrl + E 即可一键补全，效率瞬间拉满。\n本教程将提供一份详尽的、面向生产环境与本地开发环境的部署指南，并在文章后半部分梳理了 Bash/Zsh 通用的终端输入快捷键，建议收藏以备速查。\n一、部署 Zsh + Oh My Zsh 第一步：安装 zsh 基础环境并切换默认 Shell Ubuntu 纯净系统默认使用的是 Bash，我们需要先安装 Zsh 以及克隆插件所需的 git 和下载工具 curl。\n# 1. 更新包管理器并安装 zsh、git 和 curl sudo apt update \u0026amp;\u0026amp; sudo apt install zsh git curl -y # 2. 将当前用户的默认 Shell 切换为 zsh chsh -s $(which zsh) 💡 重要说明：\n执行完 chsh 切换默认 Shell 后，当前终端会话并不会立刻改变。你需要完全关闭当前终端连接（注销账户、重新打开 Terminal 窗口，或者断开当前的 SSH 连接后重新登入）。 重新进入终端后，系统会检测到首次使用 Zsh 并弹出初始化菜单（通常有 0、1、2 等选项）。直接输入 0 创建一个空白的 .zshrc 配置文件即可，因为后续步骤中 Oh My Zsh 会自动为我们生成并覆盖该配置。 可以通过命令 echo $SHELL 来验证当前默认 Shell，若输出类似 /bin/zsh 或 /usr/bin/zsh，则说明切换成功。 第二步：一键安装 Oh My Zsh 骨架 Oh My Zsh 是一个社区驱动的、用于管理 Zsh 配置的开箱即用框架。利用官方提供的标准安装脚本，我们可以将核心生态直接拉取到当前用户的家目录下：\nsh -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\u0026#34; ⚠️ 网络超时应对方案： 如果你在国内服务器或因网络限制无法连接 GitHub，导致上述命令卡死或报错，可以使用国内的 Gitee 镜像脚本进行一键安装：\nsh -c \u0026#34;$(curl -fsSL https://gitee.com/pocmon/ohmyzsh/raw/master/tools/install.sh)\u0026#34; 安装完成后，终端的提示符会变成工整的 ~ 状态，并且会看到彩色的 Oh My Zsh 标志，这说明骨架已搭建完毕。\n第三步：下载“自动补全”与“高亮”双核插件 为了实现“敲下命令前几个字母即自动浮现历史暗影提示”和“用红绿色直观判断命令拼写是否正确”的神级效果，我们需要将以下两个插件克隆到 Oh My Zsh 的插件目录中：\n# 1. 下载 zsh-autosuggestions（自动补全插件） git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions # 2. 下载 zsh-syntax-highlighting（语法高亮插件） git clone https://github.com/zsh-users/zsh-syntax-highlighting ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting zsh-autosuggestions：它会根据你此前在终端成功运行过的历史命令，在你输入时以灰色暗影形式提示。 zsh-syntax-highlighting：在你键入命令时，如果命令正确会高亮为绿色，拼写错误或命令不存在则高亮为红色，让你在按下回车前就能发现低级拼写错误。 第四步：配置 .zshrc 并合闸生效 我们需要编辑用户家目录下的 Zsh 配置文件，让 Zsh 在每次启动时自动加载这两个下载好的插件。\n1. 打开配置文件 你可以使用系统自带的 nano 文本编辑器打开：\nnano ~/.zshrc 2. 修改 plugins 数组 在文件中全局搜索 plugins=( 这行代码（默认它只启用了 git）。按方向键将光标移动至此处，修改为以下配置（插件之间使用空格或换行隔开，不要用逗号，且注意右括号闭合）：\nplugins=( git zsh-autosuggestions zsh-syntax-highlighting ) zsh-syntax-highlighting 必须放在最后一个。 它的工作方式是接管命令行的 渲染，排在它后面的插件会被它盖掉、静默失效。官方文档也是这么要求的。\n3. 保存并退出编辑器 在 nano 编辑器中：\n按下 Ctrl + O 组合键，再按 Enter 键确认保存。 按下 Ctrl + X 组合键退出编辑器。 4. 刷新终端配置使之立即生效 source ~/.zshrc 进阶：调整灰色提示的辨识度 在某些终端软件（如默认的 Xshell、MobaXterm、VS Code 内置终端等）下，由于默认的主题色设置，zsh-autosuggestions 的灰色暗影可能会显得太淡（看不清）或者太亮（误认为是已输入的字符）。\n我们可以通过在 ~/.zshrc 文件的末尾添加如下配置来手动调整暗影提示的颜色：\n# 修改自动建议的颜色（8 代表灰色，你可以根据需要调整，240~245 通常效果较佳） ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE=\u0026#39;fg=8\u0026#39; 修改完成后，再次运行 source ~/.zshrc 即可生效。\n二、Bash/Zsh 命令行快捷键 许多开发者在使用 Linux 时，习惯用方向键慢吞吞地移动光标，或者疯狂按 Backspace 删掉整行。实际上，不管是 Bash 还是 Zsh，其底层都支持 GNU Readline 的 Emacs 快捷键模式。\n熟练掌握以下核心快捷键，能让你的命令行修改速度提升十倍以上：\n1. 光标快速移动 在编辑一行长命令时，方向键的效率极低，应改用以下组合键：\n快捷键 对应动作 记忆技巧 Ctrl + A 移动光标到行首 Ahead (最前) Ctrl + E 移动光标到行尾 End (末尾) Alt + F 光标向右（向前）跳跃一个单词 Forward (向前) Alt + B 光标向左（向后）跳跃一个单词 Backward (向后) 💡 提示：在 macOS 的终端或 iTerm2 中，Alt 键通常对应 Option 键。如果发现 Alt 快捷键失效，需要在终端的偏好设置中将 Option 键映射为 Esc+ 模式（Meta 键）。\n2. 文本快速编辑与剪切/粘贴 这些快捷键类似于“剪切板”操作，被删除的文本可以重新被贴回：\n快捷键 对应动作 记忆技巧 Ctrl + U 剪切从光标处到行首的所有字符 Undo (撤销光标前的输入) Ctrl + K 剪切从光标处到行尾的所有字符 Kill (杀掉光标后的字符) Ctrl + W 剪切光标前的一个单词（以空格分隔） Word (删除前一个单词) Alt + D 剪切光标后的一个单词 Delete (删除后一个单词) Ctrl + Y 粘贴（召回）最近一次被剪切的文本 Yank (拉回/黏贴) Ctrl + H 删除光标前的一个字符（等同于退格键） - Ctrl + D 删除光标所在位置的单个字符 如果行内无内容，则代表退出(EOF) 3. 历史命令检索与复用 对于没有安装 zsh-autosuggestions 的基础环境，我们可以通过以下组合高效复用历史指令：\n快捷键 对应动作 记忆技巧 Ctrl + R 反向实时搜索历史命令 Reverse Search (递归查找) Ctrl + G 退出 Ctrl + R 历史搜索模式 - !! 代表并执行上一条执行过的命令 常用于漏打 sudo：sudo !! !$ 代表上一条命令的最后一个参数 例：mkdir mydir \u0026amp;\u0026amp; cd !$ 4. 终端控制与流程干预 用于维护当前终端状态的运行控制：\n快捷键 对应动作 记忆技巧 Ctrl + L 清空当前屏幕 效果等同于 clear 命令，但瞬间完成 Ctrl + C 强行中断当前正在运行的命令或前台进程 Cancel (取消) Ctrl + Z 将当前前台进程挂起（暂停并转入后台） 可配合 bg 或 fg 命令恢复 Ctrl + D 退出当前 Shell 会话 等同于输入 exit 或 logout 💡 总结与建议 在日常开发与工业级运维中：\n优先部署 Zsh 环境：利用 zsh-autosuggestions 的暗影补全功能，输入较长路径或重复运行 docker、git 复杂命令时可以一键搞定； 辅以快捷键：当遇到不可避免的纯 Bash 环境（例如某些只读的容器镜像或没有权限安装 Zsh 的旧服务器）时，熟练运用 Ctrl + A / E、Ctrl + R 以及 sudo !! 等快捷键，同样能保证你的操作速度不落下风。 希望这篇速查指南能够成为你命令行操作的“效率加速器”！\n","permalink":"https://www.yichengcode.com/posts/ubuntu-zsh-ohmyzsh-autocomplete/","summary":"\u003cp\u003e在 Linux 工业级运维和日常高效开发中，终端（Terminal）是我们交互最频繁的工具。默认的 Bash 虽然稳定，但在\u003cstrong\u003e自动补全\u003c/strong\u003e、\u003cstrong\u003e语法高亮\u003c/strong\u003e和\u003cstrong\u003e历史命令检索\u003c/strong\u003e等日常痛点上显得力不从心。\u003c/p\u003e\n\u003cp\u003e通过升级至 \u003cstrong\u003eZsh\u003c/strong\u003e，配合 \u003cstrong\u003eOh My Zsh\u003c/strong\u003e 生态以及核心的双核插件（\u003cstrong\u003ezsh-autosuggestions\u003c/strong\u003e 与 \u003cstrong\u003ezsh-syntax-highlighting\u003c/strong\u003e），你将在光标后获得灰色的历史命令暗影提示，只需按右方向键或 \u003ccode\u003eCtrl + E\u003c/code\u003e 即可一键补全，效率瞬间拉满。\u003c/p\u003e\n\u003cp\u003e本教程将提供一份详尽的、面向生产环境与本地开发环境的部署指南，并在文章后半部分梳理了 \u003cstrong\u003eBash/Zsh 通用的终端输入快捷键\u003c/strong\u003e，建议收藏以备速查。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"一部署-zsh--oh-my-zsh\"\u003e一、部署 Zsh + Oh My Zsh\u003c/h2\u003e\n\u003ch3 id=\"第一步安装-zsh-基础环境并切换默认-shell\"\u003e第一步：安装 zsh 基础环境并切换默认 Shell\u003c/h3\u003e\n\u003cp\u003eUbuntu 纯净系统默认使用的是 Bash，我们需要先安装 Zsh 以及克隆插件所需的 \u003ccode\u003egit\u003c/code\u003e 和下载工具 \u003ccode\u003ecurl\u003c/code\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 1. 更新包管理器并安装 zsh、git 和 curl\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt update \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e sudo apt install zsh git curl -y\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 2. 将当前用户的默认 Shell 切换为 zsh\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003echsh -s \u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003ewhich zsh\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e💡 \u003cstrong\u003e重要说明\u003c/strong\u003e：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e执行完 \u003ccode\u003echsh\u003c/code\u003e 切换默认 Shell 后，当前终端会话并不会立刻改变。你需要\u003cstrong\u003e完全关闭当前终端连接\u003c/strong\u003e（注销账户、重新打开 Terminal 窗口，或者断开当前的 SSH 连接后重新登入）。\u003c/li\u003e\n\u003cli\u003e重新进入终端后，系统会检测到首次使用 Zsh 并弹出初始化菜单（通常有 \u003ccode\u003e0\u003c/code\u003e、\u003ccode\u003e1\u003c/code\u003e、\u003ccode\u003e2\u003c/code\u003e 等选项）。直接输入 \u003ccode\u003e0\u003c/code\u003e 创建一个空白的 \u003ccode\u003e.zshrc\u003c/code\u003e 配置文件即可，因为后续步骤中 Oh My Zsh 会自动为我们生成并覆盖该配置。\u003c/li\u003e\n\u003cli\u003e可以通过命令 \u003ccode\u003eecho $SHELL\u003c/code\u003e 来验证当前默认 Shell，若输出类似 \u003ccode\u003e/bin/zsh\u003c/code\u003e 或 \u003ccode\u003e/usr/bin/zsh\u003c/code\u003e，则说明切换成功。\u003c/li\u003e\n\u003c/ol\u003e\u003c/blockquote\u003e\n\u003chr\u003e\n\u003ch3 id=\"第二步一键安装-oh-my-zsh-骨架\"\u003e第二步：一键安装 Oh My Zsh 骨架\u003c/h3\u003e\n\u003cp\u003eOh My Zsh 是一个社区驱动的、用于管理 Zsh 配置的开箱即用框架。利用官方提供的标准安装脚本，我们可以将核心生态直接拉取到当前用户的家目录下：\u003c/p\u003e","title":"Ubuntu 部署 Zsh + Oh My Zsh：自动补全、语法高亮与终端快捷键速查"},{"content":"常用 Linux 命令速查手册 📚 目录 certbot 配置 HTTPS 查看端口使用情况 查看文件或端口占用 查看系统运行状态 查看系统配置（CPU / 硬盘 / 内存） 下载工具 curl 下载工具 wget 文件查找 find 内容搜索 grep 系统清理命令（释放空间） 1. certbot 配置 HTTPS（以 Nginx 为例） 命令：\nsudo apt update sudo apt install certbot python3-certbot-nginx -y sudo certbot --nginx -d nice.yichengcode.com 命令说明：\n安装 certbot 和 nginx 插件 自动申请并配置 SSL 证书 2. 查看端口使用情况 命令：\nss -tulnp 命令说明：\n-t：显示 TCP 端口 -u：显示 UDP 端口 -l：仅列出监听端口 -n：不解析服务名，显示数字端口 -p：显示使用端口的进程信息 示例：\nss -tulnp | grep 80 3. 查看文件或端口占用 命令：\nlsof -i :端口号 命令说明：\n-i：指定网络端口或协议（如 :80、tcp） -n：不解析主机名，显示 IP -P：不解析端口名，显示端口号 -u 用户：查看某用户的打开文件 +D 路径：查看某目录下被打开的文件 示例：\nlsof -i :443 lsof -iTCP -sTCP:LISTEN -n -P 4. 查看系统运行状态 命令：\ntop 命令说明（运行中使用快捷键）：\nP：按 CPU 占用排序 M：按内存占用排序 k：输入 PID 杀死进程 1：显示所有 CPU 核心信息 q：退出 top 5. 查看系统配置（CPU / 硬盘 / 内存） 查看 CPU 信息：\nlscpu 查看内存信息：\nfree -h 查看硬盘使用情况：\ndf -h 查看所有块设备（含挂载点）：\nlsblk 查看物理磁盘详细信息：\nsudo fdisk -l 查看内存和交换空间使用情况：\nvmstat -s 查看硬件信息（需安装 lshw）：\nsudo lshw -short 6. 下载工具 curl 命令：\ncurl [参数] [URL] 命令说明：\n-O：使用 URL 中的文件名保存文件 -o 文件名：指定保存为某个文件 -L：跟随重定向 -I：仅获取响应头 -X：指定请求方法（如 GET、POST） -d：POST 数据（如 -d \u0026quot;name=value\u0026quot;） -H：添加请求头 -fsSL：常用于脚本中下载并隐藏错误（-f 出错时退出，-s 静默，-S 显示错误，-L 跟重定向） -x [协议]://[IP]:[端口]：设置代理，例如 -x socks5h://127.0.0.1:1080 示例：\ncurl -O https://example.com/file.zip curl -L -o final.html http://example.com/redirect curl -X POST -d \u0026#34;user=abc\u0026amp;pass=123\u0026#34; https://example.com/login curl -H \u0026#34;Authorization: Bearer token123\u0026#34; https://api.example.com/data curl -fsSL https://get.docker.com | sh curl -x socks5h://127.0.0.1:1080 https://api.ipify.org 7. 下载工具 wget 命令：\nwget [参数] [URL] 命令说明：\n-O 文件名：保存为指定文件名 -c：断点续传 -b：后台下载 --limit-rate=200k：限制下载速度 --no-check-certificate：忽略证书验证 示例：\nwget https://example.com/file.zip wget -c -O latest.zip https://example.com/file.zip wget --limit-rate=100k https://example.com/bigfile.iso wget -b https://example.com/large.zip 8. 文件查找 find 命令：\nfind [路径] [匹配条件] [动作] 命令说明：\n-name \u0026quot;关键词\u0026quot;：匹配文件名（支持通配符） -iname：忽略大小写的文件名匹配 -type f：查找文件（d 表示目录） -size +100M：文件大于 100MB（可换单位 K/G） -mtime -7：修改时间在 7 天以内 -exec CMD {} \\;：对每个匹配文件执行命令 示例：\n# 查找 /var 目录下所有 .log 文件 find /var -name \u0026#34;*.log\u0026#34; # 查找当前目录下最近 3 天修改的 .sh 脚本 find . -name \u0026#34;*.sh\u0026#34; -mtime -3 # 查找超过 100MB 的视频文件 find ~/Videos -type f -size +100M # 查找并删除超过 30 天未修改的 .log 文件 find /var/log -name \u0026#34;*.log\u0026#34; -mtime +30 -exec rm -f {} \\; 9. 内容搜索 grep 命令：\ngrep [选项] \u0026#34;关键词\u0026#34; [文件] 命令说明：\n-i：忽略大小写 -n：显示行号 -r：递归搜索目录 -v：反向匹配（不包含关键词） -E：支持正则表达式 --color=auto：高亮匹配词 示例：\n# 在文件中搜索 error（区分大小写） grep \u0026#34;error\u0026#34; logfile.txt # 忽略大小写 + 显示行号 grep -in \u0026#34;error\u0026#34; logfile.txt # 搜索当前目录及子目录中所有包含 api 的内容 grep -r \u0026#34;api\u0026#34; . # 使用正则表达式匹配多个关键词（如 error 或 fail） grep -E \u0026#34;error|fail\u0026#34; logfile.txt # 排除包含 success 的行 grep -v \u0026#34;success\u0026#34; logfile.txt 10. 系统清理命令（释放空间） 清理 apt 缓存和无用依赖 sudo apt clean sudo apt autoremove -y 清理日志文件（以 systemd 为例） sudo journalctl --vacuum-time=7d 命令说明：\n--vacuum-time=7d：删除早于 7 天的日志 --vacuum-size=1G：仅保留最新日志，总体积不超过 1GB --vacuum-files=5：仅保留最近 5 个日志文件 示例：\n# 保留最近 3 天日志 sudo journalctl --vacuum-time=3d # 限制日志总大小不超过 500MB sudo journalctl --vacuum-size=500M # 仅保留最近 10 个日志文件 sudo journalctl --vacuum-files=10 查找大文件（占用空间最多） sudo du -ahx / | sort -rh | head -20 删除 core dump 文件 sudo find / -name \u0026#34;core\u0026#34; -type f -exec rm -f {} \\; 查看磁盘空间占用前 10 的目录 sudo du -h --max-depth=1 / | sort -hr | head -n 10 ","permalink":"https://www.yichengcode.com/posts/linux-commands/","summary":"\u003ch2 id=\"常用-linux-命令速查手册\"\u003e常用 Linux 命令速查手册\u003c/h2\u003e\n\u003ch3 id=\"-目录\"\u003e📚 目录\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003ca href=\"#1-certbot-%E9%85%8D%E7%BD%AE-https%E4%BB%A5-nginx-%E4%B8%BA%E4%BE%8B\"\u003ecertbot 配置 HTTPS\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#2-%E6%9F%A5%E7%9C%8B%E7%AB%AF%E5%8F%A3%E4%BD%BF%E7%94%A8%E6%83%85%E5%86%B5\"\u003e查看端口使用情况\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#3-%E6%9F%A5%E7%9C%8B%E6%96%87%E4%BB%B6%E6%88%96%E7%AB%AF%E5%8F%A3%E5%8D%A0%E7%94%A8\"\u003e查看文件或端口占用\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#4-%E6%9F%A5%E7%9C%8B%E7%B3%BB%E7%BB%9F%E8%BF%90%E8%A1%8C%E7%8A%B6%E6%80%81\"\u003e查看系统运行状态\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#5-%E6%9F%A5%E7%9C%8B%E7%B3%BB%E7%BB%9F%E9%85%8D%E7%BD%AEcpu--%E7%A1%AC%E7%9B%98--%E5%86%85%E5%AD%98\"\u003e查看系统配置（CPU / 硬盘 / 内存）\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#6-%E4%B8%8B%E8%BD%BD%E5%B7%A5%E5%85%B7-curl\"\u003e下载工具 curl\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#7-%E4%B8%8B%E8%BD%BD%E5%B7%A5%E5%85%B7-wget\"\u003e下载工具 wget\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#8-%E6%96%87%E4%BB%B6%E6%9F%A5%E6%89%BE-find\"\u003e文件查找 find\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#9-%E5%86%85%E5%AE%B9%E6%90%9C%E7%B4%A2-grep\"\u003e内容搜索 grep\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#10-%E7%B3%BB%E7%BB%9F%E6%B8%85%E7%90%86%E5%91%BD%E4%BB%A4%E9%87%8A%E6%94%BE%E7%A9%BA%E9%97%B4\"\u003e系统清理命令（释放空间）\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch3 id=\"1-certbot-配置-https以-nginx-为例\"\u003e1. certbot 配置 HTTPS（以 Nginx 为例）\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003e命令\u003c/strong\u003e：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt update\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt install certbot python3-certbot-nginx -y\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo certbot --nginx -d nice.yichengcode.com\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e命令说明\u003c/strong\u003e：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e安装 certbot 和 nginx 插件\u003c/li\u003e\n\u003cli\u003e自动申请并配置 SSL 证书\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch3 id=\"2-查看端口使用情况\"\u003e2. 查看端口使用情况\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003e命令\u003c/strong\u003e：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ess -tulnp\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e命令说明\u003c/strong\u003e：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003e-t\u003c/code\u003e：显示 TCP 端口\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e-u\u003c/code\u003e：显示 UDP 端口\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e-l\u003c/code\u003e：仅列出监听端口\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e-n\u003c/code\u003e：不解析服务名，显示数字端口\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e-p\u003c/code\u003e：显示使用端口的进程信息\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e示例\u003c/strong\u003e：\u003c/p\u003e","title":"Linux Commands"},{"content":" 本文整理自 vim cheat sheet 与实际使用经验，覆盖从基本移动、插入、编辑、寄存器、宏、折叠到 diff 模式等常用命令，适合日常参考使用。\n基础命令 命令 说明 :h {keyword} 查看帮助文档 :saveas {文件名} 另存为 :close 关闭当前窗口 :terminal 打开终端窗口 vimtutor 启动 Vim 教程 光标移动 命令 说明 h 左移一个字符 j 下移一行 k 上移一行 l 右移一个字符 0 移动到行首 ^ 移动到本行第一个非空字符 $ 移动到行尾 w 跳到下一个单词开头 e 跳到单词末尾 b 跳到上一个单词开头 gg 跳到文件首行 G 跳到文件尾行 H 移动到屏幕顶部 M 移动到屏幕中间 L 移动到屏幕底部 Ctrl+d 向下滚动半屏 Ctrl+u 向上滚动半屏 Ctrl+f 向下滚动一屏 Ctrl+b 向上滚动一屏 % 跳转到匹配的括号 插入模式 命令 说明 i 在光标前插入 I 在行首插入 a 在光标后插入 A 在行尾插入 o 在当前行下方新开一行并插入 O 在当前行上方新开一行并插入 ea 在单词末尾插入 Ctrl+h 删除前一个字符（退格） Ctrl+w 删除前一个单词 Ctrl+u 删除整行插入内容 Ctrl+o 在插入模式下执行一条普通模式命令 编辑文本 命令 说明 r{char} 替换光标所在字符为{char} R 进入替换模式 J 合并下一行到当前行末尾 gJ 合并下一行到当前行末尾，不加空格 cc 修改整行（删除后进入插入模式） cw 修改从光标到单词末尾 ciw 修改光标所在的整个单词 S 整行替换（等同于 cc） s 删除光标所在字符并进入插入模式 xp 交换光标所在字符与下一字符 u 撤销 U 撤销整行修改 . 重复上一次修改命令 可视化模式与选择 命令 说明 v 进入字符可视模式 V 进入行可视模式 Ctrl+v 进入块可视模式 ab 选择一个块（包括空格） ib 选择一个块（不包括空格） at 选择一个标签块（包括标签） it 选择一个标签块（不包括标签） 剪切、复制、粘贴 命令 说明 yy 复制当前行 dd 删除当前行（剪切） p 粘贴到光标后 P 粘贴到光标前 gp 粘贴后光标停留在文本末尾 diw 删除光标所在单词 yaw 复制光标所在单词（包括空格） daw 删除光标所在单词（包括空格） :d 删除当前行 :.,$d 删除从当前行到文件末尾 :g/{pattern}/d 删除所有匹配模式的行 寄存器 Vim 中的寄存器用于存储文本，常用寄存器如下：\n寄存器 说明 \u0026quot; 默认寄存器，保存最近的删除或复制内容 0 保存最近的复制（yank）内容 1 - 9 保存最近的删除内容，1 是最近一次，9 是最旧的 - 保存最近的小删除内容（小于一行） . 保存上一次插入的文本 % 当前文件名 : 最近执行的命令 # 上一个文件名 _ 黑洞寄存器，丢弃内容（删除时不保存） + 系统剪贴板（X11、Windows 剪贴板） * 选择缓冲区（鼠标选择时） = 表达式寄存器，可以执行表达式并插入结果 使用示例：\n\u0026quot;ayy 复制当前行到寄存器 a \u0026quot;ap 粘贴寄存器 a 的内容 标记与跳转 命令 说明 ma 在当前光标位置设置标记 a `a 跳转到标记 a 的精确位置 'a 跳转到标记 a 所在行首 '. 跳转到上次编辑位置 `` 跳转到上次跳转位置 :marks 列出所有标记 Ctrl+i 前进跳转位置（类似浏览器前进） Ctrl+o 后退跳转位置（类似浏览器后退） g; 跳转到上一个修改位置 g, 跳转到下一个修改位置 宏录制与执行 命令 说明 qa 开始录制宏到寄存器 a q 停止录制 @a 执行寄存器 a 中的宏 @@ 重复执行上次宏 查找与替换 命令 说明 /pattern 向下搜索 pattern ?pattern 向上搜索 pattern n 重复上次搜索，方向相同 N 重复上次搜索，方向相反 :%s/old/new/g 全文替换 old 为 new :noh 取消高亮搜索结果 :vimgrep /pattern/ {files} 在指定文件中搜索 pattern :cn 跳转到下一个搜索结果 :cp 跳转到上一个搜索结果 保存与退出 命令 说明 :w 保存文件 :q 退出 :wq 保存并退出 ZZ 保存并退出（快捷键） :w !sudo tee % 以 sudo 权限保存文件 :wqa 保存所有文件并退出 多文件编辑与窗口操作 命令 说明 :e {filename} 打开指定文件 :bn 切换到下一个缓冲区 :bp 切换到上一个缓冲区 :b {buffer} 切换到指定缓冲区 :ls 列出所有缓冲区 :split 或 :sp 水平分割窗口 :vsplit 或 :vsp 垂直分割窗口 Ctrl+w s 水平分割 Ctrl+w v 垂直分割 Ctrl+w w 切换窗口 Ctrl+w h/j/k/l 移动到左/下/上/右窗口 Ctrl+w c 关闭当前窗口 Ctrl+w o 关闭其他窗口 标签页管理 命令 说明 :tabnew 新建标签页 gt 切换到下一个标签页 gT 切换到上一个标签页 :tabclose 关闭当前标签页 :tabdo {cmd} 在所有标签页执行命令 缩进 命令 说明 \u0026lt;\u0026lt; 左缩进一层 \u0026gt;\u0026gt; 右缩进一层 = 自动缩进当前行或选中区域 \u0026gt;ib 右缩进块内内容 \u0026lt;at 左缩进标签内内容 gg=G 自动缩进整个文件 ]p 粘贴并调整缩进 折叠 命令 说明 za 折叠/展开当前折叠 zo 展开当前折叠 zc 折叠当前折叠 zr 减少折叠级别，展开更多 zm 增加折叠级别，折叠更多 zi 切换折叠功能开关 Diff 模式 命令 说明 :diffthis 当前窗口进入 diff 模式 do 将差异内容从另一窗口合并到当前窗口 dp 将差异内容从当前窗口合并到另一窗口 :diffoff 关闭 diff 模式 ]c 跳转到下一个差异块 [c 跳转到上一个差异块 这份表适合放在手边长期查阅。命令记不清时，Vim 自带的帮助最权威： :h 命令名（比如 :h ciw）。更多速查可参考 vim.rtorr.com。\n","permalink":"https://www.yichengcode.com/posts/vim/","summary":"\u003cblockquote\u003e\n\u003cp\u003e本文整理自 \u003ca href=\"https://vim.rtorr.com/zh/\"\u003evim cheat sheet\u003c/a\u003e 与实际使用经验，覆盖从基本移动、插入、编辑、寄存器、宏、折叠到 diff 模式等常用命令，适合日常参考使用。\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"基础命令\"\u003e基础命令\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e命令\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e:h {keyword}\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e查看帮助文档\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e:saveas {文件名}\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e另存为\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e:close\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e关闭当前窗口\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e:terminal\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e打开终端窗口\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003evimtutor\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e启动 Vim 教程\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"光标移动\"\u003e光标移动\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e命令\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eh\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e左移一个字符\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ej\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e下移一行\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ek\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e上移一行\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003el\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e右移一个字符\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e0\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e移动到行首\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e^\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e移动到本行第一个非空字符\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e$\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e移动到行尾\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ew\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e跳到下一个单词开头\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ee\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e跳到单词末尾\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eb\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e跳到上一个单词开头\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003egg\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e跳到文件首行\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eG\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e跳到文件尾行\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eH\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e移动到屏幕顶部\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eM\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e移动到屏幕中间\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eL\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e移动到屏幕底部\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eCtrl+d\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e向下滚动半屏\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eCtrl+u\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e向上滚动半屏\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eCtrl+f\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e向下滚动一屏\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eCtrl+b\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e向上滚动一屏\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e%\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e跳转到匹配的括号\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"插入模式\"\u003e插入模式\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e命令\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ei\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e在光标前插入\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eI\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e在行首插入\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ea\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e在光标后插入\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eA\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e在行尾插入\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eo\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e在当前行下方新开一行并插入\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eO\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e在当前行上方新开一行并插入\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eea\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e在单词末尾插入\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eCtrl+h\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e删除前一个字符（退格）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eCtrl+w\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e删除前一个单词\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eCtrl+u\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e删除整行插入内容\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eCtrl+o\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e在插入模式下执行一条普通模式命令\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"编辑文本\"\u003e编辑文本\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e命令\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003er{char}\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e替换光标所在字符为{char}\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eR\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e进入替换模式\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eJ\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e合并下一行到当前行末尾\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003egJ\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e合并下一行到当前行末尾，不加空格\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ecc\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e修改整行（删除后进入插入模式）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ecw\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e修改从光标到单词末尾\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eciw\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e修改光标所在的整个单词\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eS\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e整行替换（等同于 cc）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003es\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e删除光标所在字符并进入插入模式\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003exp\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e交换光标所在字符与下一字符\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eu\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e撤销\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eU\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e撤销整行修改\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e.\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e重复上一次修改命令\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"可视化模式与选择\"\u003e可视化模式与选择\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e命令\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ev\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e进入字符可视模式\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eV\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e进入行可视模式\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eCtrl+v\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e进入块可视模式\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eab\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e选择一个块（包括空格）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eib\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e选择一个块（不包括空格）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eat\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e选择一个标签块（包括标签）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eit\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e选择一个标签块（不包括标签）\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"剪切复制粘贴\"\u003e剪切、复制、粘贴\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e命令\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eyy\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e复制当前行\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003edd\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e删除当前行（剪切）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ep\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e粘贴到光标后\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eP\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e粘贴到光标前\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003egp\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e粘贴后光标停留在文本末尾\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ediw\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e删除光标所在单词\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eyaw\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e复制光标所在单词（包括空格）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003edaw\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e删除光标所在单词（包括空格）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e:d\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e删除当前行\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e:.,$d\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e删除从当前行到文件末尾\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e:g/{pattern}/d\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e删除所有匹配模式的行\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"寄存器\"\u003e寄存器\u003c/h2\u003e\n\u003cp\u003eVim 中的寄存器用于存储文本，常用寄存器如下：\u003c/p\u003e","title":"Vim 命令大全速查表"},{"content":"在开发和运维中，经常需要在 SQLite 和 MySQL 之间导入导出数据。本篇总结这两个数据库系统的常用导入导出命令，涵盖整库和单表操作，适用于备份、迁移和初始化场景。\n一、SQLite3 命令详解 导出整个数据库为 SQL 文件 sqlite3 data.db .dump \u0026gt; backup.sql data.db 是数据库文件名； .dump 会导出数据库结构 + 全部数据； 生成的 backup.sql 可用 .read 或 \u0026lt; 命令导入。 导出单个表为 SQL 文件 sqlite3 data.db \u0026#34;.dump users\u0026#34; \u0026gt; users.sql 只导出 users 表的数据和结构。\n导入 SQL 文件到 SQLite 数据库 sqlite3 data.db \u0026lt; backup.sql 或者交互模式下：\nsqlite3 data.db sqlite\u0026gt; .read backup.sql 导出为 CSV 文件 sqlite3 data.db sqlite\u0026gt; .headers on sqlite\u0026gt; .mode csv sqlite\u0026gt; .output users.csv sqlite\u0026gt; SELECT * FROM users; 说明：\n.headers on：写出表头； .mode csv：设置导出为 CSV 格式； .output：指定输出文件； SELECT：执行查询写入文件。 从 CSV 导入数据 sqlite3 data.db sqlite\u0026gt; .mode csv sqlite\u0026gt; .import --skip 1 users.csv users 两个必须知道的点：\n目标表必须已存在，.import 不会替你建表； --skip 1 不能省。CSV 第一行通常是表头（id,name,email），不跳过的话 这行会被当成一条真实数据插进去，之后查出来会多一条 id 等于字符串 \u0026quot;id\u0026quot; 的脏记录——不报错，所以很容易一直没发现。 旧版本 sqlite3 没有 --skip 参数，那就先把表头行删掉再导。\n二、MySQL 命令详解 导出整个数据库 mysqldump -u root -p --single-transaction mydb \u0026gt; backup.sql 参数说明：\n参数 含义 不加会怎样 -u root 用户名 — -p 提示输入密码 密码写在命令行里会进 shell 历史，也会被 ps 看到 --single-transaction 用一致性快照导出，不锁表 InnoDB 表在导出期间被锁住，线上写不进去 mydb 要导出的数据库名 — --single-transaction 只对 InnoDB 有效，MyISAM 表还是会被锁。\n导出单个表 mysqldump -u root -p mydb users \u0026gt; users.sql 只导出 users 表的数据和结构。\n导入 SQL 文件到 MySQL mysql -u root -p mydb \u0026lt; backup.sql 或在交互模式中执行：\nmysql -u root -p mysql\u0026gt; USE mydb; mysql\u0026gt; SOURCE /path/to/file.sql; 导出表为 CSV（仅限服务器路径） SELECT * FROM users INTO OUTFILE \u0026#39;/tmp/users.csv\u0026#39; FIELDS TERMINATED BY \u0026#39;,\u0026#39; ENCLOSED BY \u0026#39;\u0026#34;\u0026#39; LINES TERMINATED BY \u0026#39;\\n\u0026#39;; 这条经常直接报 The MySQL server is running with the --secure-file-priv option。 原因是 MySQL 限定了只能读写某一个目录，先查出来是哪个：\nSHOW VARIABLES LIKE \u0026#39;secure_file_priv\u0026#39;; 返回一个路径：只能往这个目录里写，/tmp 多半不行； 返回空：不限制； 返回 NULL：功能被彻底禁用，INTO OUTFILE 用不了。 另外文件是 MySQL 服务进程写的，落在服务器上而不是你的电脑上。想直接导到本地， 改用客户端来做：\nmysql -u root -p -e \u0026#34;SELECT * FROM users\u0026#34; mydb | sed \u0026#39;s/\\t/,/g\u0026#39; \u0026gt; users.csv 从 CSV 导入数据 LOAD DATA INFILE \u0026#39;/tmp/users.csv\u0026#39; INTO TABLE users FIELDS TERMINATED BY \u0026#39;,\u0026#39; ENCLOSED BY \u0026#39;\u0026#34;\u0026#39; LINES TERMINATED BY \u0026#39;\\n\u0026#39; IGNORE 1 LINES; -- 跳过表头，和 SQLite 的 --skip 1 是一回事 文件在你本地而不是服务器上，就得加 LOCAL：\nLOAD DATA LOCAL INFILE \u0026#39;/home/me/users.csv\u0026#39; INTO TABLE users ... LOCAL 需要服务端和客户端都打开 local_infile（SET GLOBAL local_infile=1， 客户端加 --local-infile=1），MySQL 8 默认是关的。\n三、总结对比表 操作 SQLite3 MySQL 导出整库 .dump \u0026gt; file.sql mysqldump db \u0026gt; file.sql 导出单表 .dump table mysqldump db table \u0026gt; file.sql 导入 SQL .read file.sql / \u0026lt; file.sql mysql db \u0026lt; file.sql 导出 CSV .mode csv + SELECT SELECT INTO OUTFILE 导入 CSV .import file.csv table（需预建表） LOAD DATA INFILE（需服务端路径权限） 这些命令覆盖了日常开发中常见的数据库迁移、表结构导出、数据备份等任务场景，建议熟练掌握并结合脚本使用。\n","permalink":"https://www.yichengcode.com/posts/sqlite-mysql-import-export/","summary":"\u003cp\u003e在开发和运维中，经常需要在 SQLite 和 MySQL 之间导入导出数据。本篇总结这两个数据库系统的常用导入导出命令，涵盖整库和单表操作，适用于备份、迁移和初始化场景。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"一sqlite3-命令详解\"\u003e一、SQLite3 命令详解\u003c/h2\u003e\n\u003ch3 id=\"导出整个数据库为-sql-文件\"\u003e导出整个数据库为 SQL 文件\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esqlite3 data.db .dump \u0026gt; backup.sql\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e\u003ccode\u003edata.db\u003c/code\u003e 是数据库文件名；\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e.dump\u003c/code\u003e 会导出数据库结构 + 全部数据；\u003c/li\u003e\n\u003cli\u003e生成的 \u003ccode\u003ebackup.sql\u003c/code\u003e 可用 \u003ccode\u003e.read\u003c/code\u003e 或 \u003ccode\u003e\u0026lt;\u003c/code\u003e 命令导入。\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch3 id=\"导出单个表为-sql-文件\"\u003e导出单个表为 SQL 文件\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esqlite3 data.db \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;.dump users\u0026#34;\u003c/span\u003e \u0026gt; users.sql\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e只导出 \u003ccode\u003eusers\u003c/code\u003e 表的数据和结构。\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"导入-sql-文件到-sqlite-数据库\"\u003e导入 SQL 文件到 SQLite 数据库\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esqlite3 data.db \u0026lt; backup.sql\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e或者交互模式下：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esqlite3 data.db\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esqlite\u0026gt; .read backup.sql\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003chr\u003e\n\u003ch3 id=\"导出为-csv-文件\"\u003e导出为 CSV 文件\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esqlite3 data.db\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esqlite\u0026gt; .headers on\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esqlite\u0026gt; .mode csv\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esqlite\u0026gt; .output users.csv\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esqlite\u0026gt; SELECT * FROM users;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e说明：\u003c/p\u003e","title":"SQLite 与 MySQL 导入导出速查：整库、单表与 CSV"},{"content":"在 Linux 系统中，update-alternatives 是一种用于管理命令软链接的工具，适合管理多个版本共存的程序，例如不同版本的 PHP、Java、GCC、Python 等。通过它可以灵活控制哪个版本被调用、切换默认版本、统一管理系统路径下的可执行程序。\n一、基本原理 Linux 中大多数命令其实都是 /usr/bin/xxx 的可执行文件，这些文件很多是软链接：\nls -l /usr/bin/php # 可能指向 -\u0026gt; /etc/alternatives/php # 再指向 -\u0026gt; /usr/bin/php8.2 update-alternatives 就是负责管理 /etc/alternatives/ 中的这些链接，并允许你注册多个候选路径、设置优先级、手动切换或自动切换。\n二、常用参数 参数 说明 --install 添加一个新候选版本 --remove 移除某个候选路径 --config 手动选择当前使用哪个版本 --set 直接设置当前使用路径 --display / --query 查看当前配置状态 --auto 恢复为优先级自动选择模式 \u0026ndash;install 的四个位置参数 update-alternatives --install 是添加“候选版本”的关键命令，它的完整语法格式如下：\nsudo update-alternatives --install \u0026lt;链接路径\u0026gt; \u0026lt;名字\u0026gt; \u0026lt;实际可执行路径\u0026gt; \u0026lt;优先级\u0026gt; 参数位置 含义 示例值 说明 链接路径 创建的统一入口点 /usr/bin/php 这是用户最终执行命令时调用的路径 名字 系统识别用的名称 php 是“组名”，管理同类版本的别名 实际路径 程序的真实可执行文件路径 /usr/bin/php7.4 要加入的某个版本路径 优先级 一个整数 74 数值越大，优先级越高，自动模式下会选择它 实例 你系统中已经安装了 PHP 7.4 和 PHP 8.2，可以这样添加：\nsudo update-alternatives --install /usr/bin/php php /usr/bin/php7.4 74 sudo update-alternatives --install /usr/bin/php php /usr/bin/php8.2 82 结果：\n会建立 /etc/alternatives/php → /usr/bin/php8.2 同时 /usr/bin/php 会是最终入口，间接指向 8.2 几个要点 这个命令并不会删除已存在的链接，只是“添加一个候选项”； 优先级影响“自动模式”（--auto）下的选择； 名字 一定要保持一致，比如 php，用来归组管理； 不支持自动创建 实际路径，它必须已经存在。 三、使用场景 管理多个 PHP 版本 添加候选版本 sudo update-alternatives --install /usr/bin/php php /usr/bin/php7.4 74 sudo update-alternatives --install /usr/bin/php php /usr/bin/php8.2 82 手动切换版本 sudo update-alternatives --config php 终端提示选择：\nThere are 2 choices for the alternative php (providing /usr/bin/php). Selection Path Priority Status ------------------------------------------------------------ * 0 /usr/bin/php8.2 82 auto mode 1 /usr/bin/php7.4 74 manual mode 2 /usr/bin/php8.2 82 manual mode Press \u0026lt;enter\u0026gt; to keep the current choice[*], or type selection number: 这张表比看上去绕一点：0 不是某个版本，而是「自动模式」——跟着优先级走， 装了更高优先级的新版本会自动切过去。1 和 2 才是具体版本，选了它们就进入 手动模式，之后无论装什么新版本都不再自动变。所以同一个 php8.2 会出现两次： 一次作为自动模式的当前结果，一次作为可手动锁定的选项。\n行首的 * 表示当前生效的是哪一项。\n设置为自动根据优先级 sudo update-alternatives --auto php 查看候选状态 sudo update-alternatives --display php 输出信息包含当前使用版本、优先级、是否手动模式等。\n删除某个版本 sudo update-alternatives --remove php /usr/bin/php7.4 四、适合用它管的程序 php java gcc / g++ editor（vim / nano / emacs） awk / sh python——不推荐，见下 五、注意事项 使用 --install 的路径必须真实存在； 不要拿它接管 python：Ubuntu / Debian 有大量系统脚本按固定版本调用 python，把 /usr/bin/python 指向别的版本会让 apt、系统工具莫名其妙地报错。 要切 Python 版本用虚拟环境（venv）或 pyenv； 它不会安装程序本身，只是管理已存在的路径和链接； 修改的是系统范围的命令，需 sudo 权限。 小结 update-alternatives 是 Linux 官方提供的软链接选择器，适用于系统中存在多个程序版本时的路径控制问题。它不负责安装或卸载软件，而是通过注册路径和配置优先级，实现快速切换和统一入口的目的。\n对于开发者或运维人员，合理利用该命令可以让系统更可控、更灵活。\n","permalink":"https://www.yichengcode.com/posts/update-alternatives/","summary":"\u003cp\u003e在 Linux 系统中，\u003ccode\u003eupdate-alternatives\u003c/code\u003e 是一种用于管理命令软链接的工具，适合管理多个版本共存的程序，例如不同版本的 PHP、Java、GCC、Python 等。通过它可以灵活控制哪个版本被调用、切换默认版本、统一管理系统路径下的可执行程序。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"一基本原理\"\u003e一、基本原理\u003c/h2\u003e\n\u003cp\u003eLinux 中大多数命令其实都是 \u003ccode\u003e/usr/bin/xxx\u003c/code\u003e 的可执行文件，这些文件很多是软链接：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003els -l /usr/bin/php\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 可能指向 -\u0026gt; /etc/alternatives/php\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 再指向 -\u0026gt; /usr/bin/php8.2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003eupdate-alternatives\u003c/code\u003e 就是负责管理 \u003ccode\u003e/etc/alternatives/\u003c/code\u003e 中的这些链接，并允许你注册多个候选路径、设置优先级、手动切换或自动切换。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"二常用参数\"\u003e二、常用参数\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e参数\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e--install\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e添加一个新候选版本\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e--remove\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e移除某个候选路径\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e--config\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e手动选择当前使用哪个版本\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e--set\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e直接设置当前使用路径\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e--display\u003c/code\u003e / \u003ccode\u003e--query\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e查看当前配置状态\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e--auto\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e恢复为优先级自动选择模式\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch3 id=\"install-的四个位置参数\"\u003e\u0026ndash;install 的四个位置参数\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003eupdate-alternatives --install\u003c/code\u003e 是添加“候选版本”的关键命令，它的完整语法格式如下：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo update-alternatives --install \u0026lt;链接路径\u0026gt; \u0026lt;名字\u0026gt; \u0026lt;实际可执行路径\u0026gt; \u0026lt;优先级\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e参数位置\u003c/th\u003e\n          \u003cth\u003e含义\u003c/th\u003e\n          \u003cth\u003e示例值\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e链接路径\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e创建的统一入口点\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003e/usr/bin/php\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e这是用户最终执行命令时调用的路径\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e名字\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e系统识别用的名称\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003ephp\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e是“组名”，管理同类版本的别名\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e实际路径\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e程序的真实可执行文件路径\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003e/usr/bin/php7.4\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e要加入的某个版本路径\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003e优先级\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e一个整数\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003e74\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e数值越大，优先级越高，自动模式下会选择它\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch4 id=\"实例\"\u003e实例\u003c/h4\u003e\n\u003cp\u003e你系统中已经安装了 PHP 7.4 和 PHP 8.2，可以这样添加：\u003c/p\u003e","title":"update-alternatives 使用笔记：多版本共存的软链接管理器"},{"content":"在 MongoDB 中，用户建在哪个库决定了它的作用范围和管理方式，这一点和 MySQL 很不一样，也是新手最容易绕进去的地方。本文讲清楚两种用户的区别、各自的使用场景、 完整的管理操作，以及连接时那个必须写对的 authSource。\n零、先确认认证是开着的 MongoDB 默认不开认证。 没开的时候，你建的用户一个都不生效——任何人连上来 都是完全权限，db.createUser() 只是往集合里写了几行数据而已。\n编辑 /etc/mongod.conf：\nsecurity: authorization: enabled 改完重启，之后所有操作都需要认证：\nsudo systemctl restart mongod 顺序很重要：先在没开认证的状态下建好第一个管理员账号，再打开认证重启。 反过来的话，认证开着但一个用户都没有，你就把自己关在门外了 （只能用 --noauth 重启一次救回来）。\n一、两种类型的用户 1. admin 数据库创建的用户（全局用户） 这种用户是在 admin 数据库中创建的，可以访问多个数据库，并对多个数据库赋予不同权限。\nuse admin db.createUser({ user: \u0026#34;adminUser\u0026#34;, pwd: \u0026#34;secure123\u0026#34;, roles: [ { role: \u0026#34;readWrite\u0026#34;, db: \u0026#34;sales\u0026#34; }, { role: \u0026#34;read\u0026#34;, db: \u0026#34;analytics\u0026#34; } ] }) 特点 用户信息存储在 admin 库； 可以管理和访问多个数据库； 适合平台管理员、系统服务等集中权限场景。 使用场景 运维或 DevOps 脚本； 应用需要访问多个数据库； 多库统一管理权限。 2. 普通数据库创建的用户（局部用户） 这种用户只能在某一个数据库中创建，默认仅能访问该数据库。\nuse appdb db.createUser({ user: \u0026#34;appUser\u0026#34;, pwd: \u0026#34;pass123\u0026#34;, roles: [ { role: \u0026#34;readWrite\u0026#34;, db: \u0026#34;appdb\u0026#34; } ] }) 特点 用户信息只存在于当前库； 默认只能访问当前数据库； 隔离性好，更安全。 使用场景 Web 应用访问自己的数据； 每个业务数据库独立授权； 多租户系统中的租户隔离。 二、常见用户管理操作 创建用户 db.createUser({ user: \u0026#34;user1\u0026#34;, pwd: \u0026#34;123456\u0026#34;, roles: [ { role: \u0026#34;readWrite\u0026#34;, db: \u0026#34;mydb\u0026#34; } ] }) 修改用户密码 db.updateUser(\u0026#34;user1\u0026#34;, { pwd: \u0026#34;newpass\u0026#34; }) 要在用户所属数据库执行。\n追加权限 要在这个用户所在的库里执行——用户建在 admin 就 use admin， 建在 appdb 就 use appdb。跑错库会报 User 'user1' not found：\ndb.grantRolesToUser(\u0026#34;user1\u0026#34;, [ { role: \u0026#34;read\u0026#34;, db: \u0026#34;logdb\u0026#34; } ]) 撤销某个权限 db.revokeRolesFromUser(\u0026#34;user1\u0026#34;, [ { role: \u0026#34;read\u0026#34;, db: \u0026#34;logdb\u0026#34; } ]) 删除用户 db.dropUser(\u0026#34;user1\u0026#34;) 查看用户与权限 db.getUsers() db.getUser(\u0026#34;user1\u0026#34;) 三、连接时的 authSource 这是实际用起来最容易卡住的一个点。认证在哪个库做，和你要访问哪个库，是两件事。\n用户建在 admin 里、要访问 sales，连接串必须告诉 MongoDB 去 admin 认证：\nmongodb://adminUser:secure123@127.0.0.1:27017/sales?authSource=admin 不写 authSource，驱动默认拿路径上那个库（这里是 sales）当认证库， 于是去 sales 里找 adminUser——找不到，报\nAuthentication failed. 密码明明是对的，但错误信息只说认证失败，很容易往密码上找原因。\n用户建在哪 连接串怎么写 admin .../appdb?authSource=admin appdb .../appdb（认证库和目标库同一个，可以省略） 四、不能直接修改用户名 MongoDB 不支持改用户名，推荐做法：\n创建新用户； 复制权限； 删除旧用户。 db.createUser({ user: \u0026#34;newUser\u0026#34;, pwd: \u0026#34;abc123\u0026#34;, roles: [ { role: \u0026#34;readWrite\u0026#34;, db: \u0026#34;mydb\u0026#34; } ] }) db.dropUser(\u0026#34;oldUser\u0026#34;) 五、最佳实践建议 管理类账号建在 admin，应用账号建在自己的业务库里； 一个应用一个账号，不要几个站共用一个——出事时你没法知道是谁泄的， 改密码也会波及所有人； 只给需要的权限：只读的业务给 read，别顺手给 readWrite； 例子里的 secure123、123456 只是占位，真实密码用随机串， 并且不要写进代码仓库。 MongoDB 的用户管理不只关系数据安全，也决定了系统好不好维护。分清 admin 用户和 局部用户各自的位置，再把 authSource 写对，权限模型就清晰了。\n数据库账号该怎么按站拆分、怎么限制来源 IP，MySQL 那边的思路是相通的， 可以接着看 MySQL 用户最佳实践。\n","permalink":"https://www.yichengcode.com/posts/mongodb-user-manager/","summary":"\u003cp\u003e在 MongoDB 中，用户\u003cstrong\u003e建在哪个库\u003c/strong\u003e决定了它的作用范围和管理方式，这一点和 MySQL\n很不一样，也是新手最容易绕进去的地方。本文讲清楚两种用户的区别、各自的使用场景、\n完整的管理操作，以及连接时那个必须写对的 \u003ccode\u003eauthSource\u003c/code\u003e。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"零先确认认证是开着的\"\u003e零、先确认认证是开着的\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eMongoDB 默认不开认证。\u003c/strong\u003e 没开的时候，你建的用户一个都不生效——任何人连上来\n都是完全权限，\u003ccode\u003edb.createUser()\u003c/code\u003e 只是往集合里写了几行数据而已。\u003c/p\u003e\n\u003cp\u003e编辑 \u003ccode\u003e/etc/mongod.conf\u003c/code\u003e：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003esecurity\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003eauthorization\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eenabled\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e改完重启，之后所有操作都需要认证：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl restart mongod\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e顺序很重要\u003c/strong\u003e：先在没开认证的状态下建好第一个管理员账号，再打开认证重启。\n反过来的话，认证开着但一个用户都没有，你就把自己关在门外了\n（只能用 \u003ccode\u003e--noauth\u003c/code\u003e 重启一次救回来）。\u003c/p\u003e\u003c/blockquote\u003e\n\u003chr\u003e\n\u003ch2 id=\"一两种类型的用户\"\u003e一、两种类型的用户\u003c/h2\u003e\n\u003ch3 id=\"1-admin-数据库创建的用户全局用户\"\u003e1. admin 数据库创建的用户（全局用户）\u003c/h3\u003e\n\u003cp\u003e这种用户是在 \u003ccode\u003eadmin\u003c/code\u003e 数据库中创建的，可以访问多个数据库，并对多个数据库赋予不同权限。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003euse\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eadmin\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edb\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ecreateUser\u003c/span\u003e({\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003euser\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;adminUser\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003epwd\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;secure123\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eroles\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    { \u003cspan style=\"color:#a6e22e\"\u003erole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;readWrite\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003edb\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;sales\u0026#34;\u003c/span\u003e },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    { \u003cspan style=\"color:#a6e22e\"\u003erole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;read\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003edb\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;analytics\u0026#34;\u003c/span\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e})\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"特点\"\u003e特点\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e用户信息存储在 \u003ccode\u003eadmin\u003c/code\u003e 库；\u003c/li\u003e\n\u003cli\u003e可以管理和访问多个数据库；\u003c/li\u003e\n\u003cli\u003e适合平台管理员、系统服务等集中权限场景。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"使用场景\"\u003e使用场景\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e运维或 DevOps 脚本；\u003c/li\u003e\n\u003cli\u003e应用需要访问多个数据库；\u003c/li\u003e\n\u003cli\u003e多库统一管理权限。\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch3 id=\"2-普通数据库创建的用户局部用户\"\u003e2. 普通数据库创建的用户（局部用户）\u003c/h3\u003e\n\u003cp\u003e这种用户只能在某一个数据库中创建，默认仅能访问该数据库。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003euse\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eappdb\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edb\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ecreateUser\u003c/span\u003e({\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003euser\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;appUser\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003epwd\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;pass123\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eroles\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e [ { \u003cspan style=\"color:#a6e22e\"\u003erole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;readWrite\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003edb\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;appdb\u0026#34;\u003c/span\u003e } ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e})\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"特点-1\"\u003e特点\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e用户信息只存在于当前库；\u003c/li\u003e\n\u003cli\u003e默认只能访问当前数据库；\u003c/li\u003e\n\u003cli\u003e隔离性好，更安全。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"使用场景-1\"\u003e使用场景\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003eWeb 应用访问自己的数据；\u003c/li\u003e\n\u003cli\u003e每个业务数据库独立授权；\u003c/li\u003e\n\u003cli\u003e多租户系统中的租户隔离。\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"二常见用户管理操作\"\u003e二、常见用户管理操作\u003c/h2\u003e\n\u003ch3 id=\"创建用户\"\u003e创建用户\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edb\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ecreateUser\u003c/span\u003e({\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003euser\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user1\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003epwd\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;123456\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eroles\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e [ { \u003cspan style=\"color:#a6e22e\"\u003erole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;readWrite\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003edb\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mydb\u0026#34;\u003c/span\u003e } ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e})\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"修改用户密码\"\u003e修改用户密码\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edb\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eupdateUser\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user1\u0026#34;\u003c/span\u003e, { \u003cspan style=\"color:#a6e22e\"\u003epwd\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;newpass\u0026#34;\u003c/span\u003e })\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e要在用户所属数据库执行。\u003c/p\u003e","title":"MongoDB 用户管理详解：admin 用户 vs 数据库用户"},{"content":"从国内直接拉 Docker Hub 的镜像，常见的表现是速度只有几十 KB/s，或者干脆卡在 Retrying in 5 seconds 反复重试。解决办法是给 Docker 配一组镜像加速源—— 它们是 Docker Hub 的只读缓存，你 docker pull 时 Docker 先去这些地址找， 找不到才回源。\n一、写入 daemon.json /etc/docker/daemon.json 是 Docker 守护进程的配置文件，registry-mirrors 就是加速源列表。配多个是为了容错：某个源挂了会自动试下一个。\n执行以下命令：\nsudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json \u0026lt;\u0026lt;EOF { \u0026#34;registry-mirrors\u0026#34;: [ \u0026#34;https://docker.1panel.live\u0026#34;, \u0026#34;https://docker.1ms.run\u0026#34;, \u0026#34;https://dytt.online\u0026#34;, \u0026#34;https://docker-0.unsee.tech\u0026#34;, \u0026#34;https://lispy.org\u0026#34;, \u0026#34;https://docker.xiaogenban1993.com\u0026#34;, \u0026#34;https://666860.xyz\u0026#34;, \u0026#34;https://hub.rat.dev\u0026#34;, \u0026#34;https://docker.m.daocloud.io\u0026#34;, \u0026#34;https://demo.52013120.xyz\u0026#34;, \u0026#34;https://proxy.vvvv.ee\u0026#34; ] } EOF sudo systemctl daemon-reload sudo systemctl restart docker 这些镜像源来自 GitHub 项目 dongyubin/DockerHub， 那里有一份持续更新的可用列表。\n两条命令分别在做什么：\n命令 作用 不执行会怎样 systemctl daemon-reload 让 systemd 重新读取单元文件 改的是 Docker 自己的配置时其实可以不做，但改过 docker.service 就必须做 systemctl restart docker 重启 Docker 守护进程 daemon.json 不会生效——它只在守护进程启动时读一次 重启 Docker 会停掉所有没有配置重启策略的容器。生产机器上先确认容器都带了 --restart=always，或者挑个没人用的时间做。\n二、确认真的生效了 改完必须验一下，不然你不知道是加速源没生效还是源本身太慢：\ndocker info | grep -A 10 \u0026#34;Registry Mirrors\u0026#34; 能看到刚才那一串地址就说明配置读进去了。再拉个小镜像实测：\ntime docker pull hello-world 三、几个常见问题 JSON 格式错误会让 Docker 起不来。 daemon.json 里多一个逗号、少一个引号， Docker 会直接启动失败，而且 systemctl status docker 的提示不太明显。 改完先验一下格式：\npython3 -m json.tool /etc/docker/daemon.json 这些是第三方源，会失效。 名单里的地址由个人或小团队维护，随时可能关停或限速。 拉取变慢时先回来换一批，不用怀疑是自己配错了。\n加速源只对 Docker Hub 有效。 拉 ghcr.io、gcr.io、quay.io 上的镜像 不走这个列表，那些要么配各自的镜像站，要么走代理。\n如果要走代理，配的不是 daemon.json，而是 Docker 服务的环境变量：\nsudo mkdir -p /etc/systemd/system/docker.service.d sudo tee /etc/systemd/system/docker.service.d/proxy.conf \u0026lt;\u0026lt;EOF [Service] Environment=\u0026#34;HTTP_PROXY=http://127.0.0.1:7890\u0026#34; Environment=\u0026#34;HTTPS_PROXY=http://127.0.0.1:7890\u0026#34; Environment=\u0026#34;NO_PROXY=localhost,127.0.0.1\u0026#34; EOF sudo systemctl daemon-reload sudo systemctl restart docker 这一处改的是 systemd 单元，所以 daemon-reload 是必须的。关于 systemd 单元 文件的写法，可以看 systemd 单元配置实战。\n","permalink":"https://www.yichengcode.com/posts/docker-china/","summary":"\u003cp\u003e从国内直接拉 Docker Hub 的镜像，常见的表现是速度只有几十 KB/s，或者干脆卡在\n\u003ccode\u003eRetrying in 5 seconds\u003c/code\u003e 反复重试。解决办法是给 Docker 配一组\u003cstrong\u003e镜像加速源\u003c/strong\u003e——\n它们是 Docker Hub 的只读缓存，你 \u003ccode\u003edocker pull\u003c/code\u003e 时 Docker 先去这些地址找，\n找不到才回源。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"一写入-daemonjson\"\u003e一、写入 daemon.json\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003e/etc/docker/daemon.json\u003c/code\u003e 是 Docker 守护进程的配置文件，\u003ccode\u003eregistry-mirrors\u003c/code\u003e\n就是加速源列表。配多个是为了容错：某个源挂了会自动试下一个。\u003c/p\u003e\n\u003cp\u003e执行以下命令：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo mkdir -p /etc/docker\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo tee /etc/docker/daemon.json \u003cspan style=\"color:#e6db74\"\u003e\u0026lt;\u0026lt;EOF\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  \u0026#34;registry-mirrors\u0026#34;: [\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#34;https://docker.1panel.live\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#34;https://docker.1ms.run\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#34;https://dytt.online\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#34;https://docker-0.unsee.tech\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#34;https://lispy.org\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#34;https://docker.xiaogenban1993.com\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#34;https://666860.xyz\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#34;https://hub.rat.dev\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#34;https://docker.m.daocloud.io\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#34;https://demo.52013120.xyz\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#34;https://proxy.vvvv.ee\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  ]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eEOF\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl daemon-reload\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl restart docker\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这些镜像源来自 GitHub 项目 \u003ca href=\"https://github.com/dongyubin/DockerHub\"\u003edongyubin/DockerHub\u003c/a\u003e，\n那里有一份持续更新的可用列表。\u003c/p\u003e","title":"配置 Docker 国内镜像加速：daemon.json 写法、生效验证与排错"},{"content":"系统还原后，遇到以下两个问题：\n点击「设置」里的项目（如「显示设置」「系统」）没有反应 任务栏右下角的输入法图标（中/英）消失 这篇文章将分别介绍这两个问题的解决方法。\n一、无法打开「显示设置」 原因分析 Windows 设置依赖后台服务 UserManager。如果此服务被禁用或未正常启动，设置页面中的子项可能无法打开。\n解决步骤 按 Win + R，输入：\nservices.msc 回车打开「服务」管理器。\n在列表中找到：\nUser Manager 双击打开，设置为：\n启动类型：自动 如果当前未运行，点击“启动” 应用并关闭窗口，重启计算机\n二、输入法图标不显示 现象 输入法可以使用，但右下角图标不显示 切换快捷键（如 Ctrl+Shift）仍可用 解决方法 按 Ctrl + Shift + Esc 打开任务管理器\n切换到【详细信息】选项卡\n找到：\nMicrosoft IME（或 IME.exe） 右键 → 结束任务\n结束后系统会自动重启这个进程，图标一般立刻回来。如果没有，手动拉起 负责输入法图标的进程：\nctfmon.exe （Win + R 调出运行框后输入）\nctfmon.exe 是管理输入法和语言栏的后台进程，图标消失多半就是它退出了。\n小结 问题 原因 解决方法 设置子页面无法打开 UserManager 服务未启动 在 services.msc 中启动并设为自动 输入法图标不显示 IME 进程卡死或崩溃 任务管理器结束 Microsoft IME 进程，或运行 ctfmon.exe 这些操作都是无风险、无需第三方工具的官方修复方法。如果你也遇到这类问题，建议直接试试，通常都能快速恢复正常。\n","permalink":"https://www.yichengcode.com/posts/win10-settings-ime-fix/","summary":"\u003cp\u003e系统还原后，遇到以下两个问题：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e点击「设置」里的项目（如「显示设置」「系统」）没有反应\u003c/li\u003e\n\u003cli\u003e任务栏右下角的输入法图标（中/英）消失\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这篇文章将分别介绍这两个问题的解决方法。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"一无法打开显示设置\"\u003e一、无法打开「显示设置」\u003c/h2\u003e\n\u003ch3 id=\"原因分析\"\u003e原因分析\u003c/h3\u003e\n\u003cp\u003eWindows 设置依赖后台服务 \u003ccode\u003eUserManager\u003c/code\u003e。如果此服务被禁用或未正常启动，设置页面中的子项可能无法打开。\u003c/p\u003e\n\u003ch3 id=\"解决步骤\"\u003e解决步骤\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e按 \u003ccode\u003eWin + R\u003c/code\u003e，输入：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eservices.msc\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e回车打开「服务」管理器。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e在列表中找到：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eUser Manager\n\u003c/code\u003e\u003c/pre\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e双击打开，设置为：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e启动类型：\u003cstrong\u003e自动\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003e如果当前未运行，点击“启动”\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e应用并关闭窗口，\u003cstrong\u003e重启计算机\u003c/strong\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch2 id=\"二输入法图标不显示\"\u003e二、输入法图标不显示\u003c/h2\u003e\n\u003ch3 id=\"现象\"\u003e现象\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e输入法可以使用，但右下角图标不显示\u003c/li\u003e\n\u003cli\u003e切换快捷键（如 Ctrl+Shift）仍可用\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"解决方法\"\u003e解决方法\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e按 \u003ccode\u003eCtrl + Shift + Esc\u003c/code\u003e 打开任务管理器\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e切换到【详细信息】选项卡\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e找到：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eMicrosoft IME（或 IME.exe）\n\u003c/code\u003e\u003c/pre\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e右键 → 结束任务\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e结束后系统会自动重启这个进程，图标一般立刻回来。如果没有，手动拉起\n负责输入法图标的进程：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ectfmon.exe\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e（\u003ccode\u003eWin + R\u003c/code\u003e 调出运行框后输入）\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ectfmon.exe\u003c/code\u003e 是管理输入法和语言栏的后台进程，图标消失多半就是它退出了。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch2 id=\"小结\"\u003e小结\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e问题\u003c/th\u003e\n          \u003cth\u003e原因\u003c/th\u003e\n          \u003cth\u003e解决方法\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e设置子页面无法打开\u003c/td\u003e\n          \u003ctd\u003eUserManager 服务未启动\u003c/td\u003e\n          \u003ctd\u003e在 services.msc 中启动并设为自动\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e输入法图标不显示\u003c/td\u003e\n          \u003ctd\u003eIME 进程卡死或崩溃\u003c/td\u003e\n          \u003ctd\u003e任务管理器结束 Microsoft IME 进程，或运行 ctfmon.exe\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e这些操作都是无风险、无需第三方工具的官方修复方法。如果你也遇到这类问题，建议直接试试，通常都能快速恢复正常。\u003c/p\u003e","title":"Windows 10 无法打开显示设置 + 输入法图标不显示的解决方法"},{"content":"Trojan 把代理流量伪装成正常的 HTTPS，所以它必须有一张真实域名的证书—— 这是它和免证书协议最大的区别，也是这篇一半篇幅在讲证书的原因。\n本文环境：Ubuntu 20.04 / Debian 11，面板用 mhsanaei/3x-ui， 示例域名 us.example.com。\n一、安装证书工具 sudo apt update sudo apt install nginx certbot python3-certbot-nginx -y certbot 负责签发 Let\u0026rsquo;s Encrypt 证书，python3-certbot-nginx 是让它自动改 Nginx 配置的插件。\nnginx 也要装。 下一步的 certbot --nginx 是靠临时改 Nginx 配置来完成 域名验证的，机器上没有 Nginx 这条命令会直接失败。装了之后 Nginx 只在签证书时用到， 不参与代理。\n二、为域名申请 HTTPS 证书 sudo certbot --nginx -d us.example.com 请确保你的域名已经解析到当前 VPS 公网 IP。 请确保服务器的 80 和 443 端口已开放。部分 VPS 提供商要求不仅在系统内部（如通过 ufw 或 firewalld）放行端口，还需在云平台的 VPC 网络安全组 或 防火墙策略 中手动添加相应的入站规则，否则外部访问可能被阻断。 如果申请成功，证书和密钥默认保存在：\n/etc/letsencrypt/live/us.example.com/fullchain.pem\n/etc/letsencrypt/live/us.example.com/privkey.pem 三、安装 3x-ui 面板 bash \u0026lt;(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) 安装完成后，运行：\nx-ui 装完脚本会打印面板地址、端口和随机生成的初始账号密码——当场记下来， 这几个值每台机器都不一样，不是固定的 54321。忘了就在服务器上敲 x-ui 进菜单重设。\n面板端口一定要改掉默认值并设强密码。3x-ui 面板一旦被爆破，等于整台服务器被拿走。\n四、添加 Trojan 节点 打开浏览器，访问 x-ui 管理面板，添加一个新的入站配置，建议选择 Trojan 类型。\n基本设置： 入站类型：Trojan（支持 TLS） 端口：建议使用 443、8443 或其他自定义端口 SNI（Server Name Indication）：填写你的域名，例如 us.example.com 密码（Password）：用户连接所需的认证密码，自行设置，务必保密 证书配置： 证书文件路径：\n/etc/letsencrypt/live/us.example.com/fullchain.pem 私钥文件路径：\n/etc/letsencrypt/live/us.example.com/privkey.pem 其他可选项： 启用 TLS：必须勾选，Trojan 不加 TLS 就失去了伪装意义 传输方式可选 TCP、WebSocket 或 gRPC。WebSocket 更容易套 CDN，gRPC 在弱网下重连更快 Reality 配不到 Trojan 上。 Reality 是 XTLS 给 VLESS 做的传输层， 面板里它只在入站类型选 VLESS 时才出现。想用 Reality（好处是不需要域名和证书） 就得另建一个 VLESS 入站，跟这条 Trojan 是两个节点。\n示例配置 五、客户端连接 导出配置（二维码或 JSON），导入到：\nv2rayN / NekoRay（Windows） Shadowrocket / Kitsunebi（iOS） v2rayNG / Clash Meta（Android） 确保客户端使用域名连接，启用 TLS，验证 SNI 字段一致。\n六、证书自动续期 certbot 装好时会自带一个 systemd timer，每天检查两次，到期前 30 天自动续。 先确认它在跑：\nsystemctl list-timers | grep certbot sudo certbot renew --dry-run # 演练一次，不会真的续 续期之后要让 3x-ui 重新读证书。 证书文件被换成了新的，而面板是启动时把证书 读进内存的，不重载就会在 90 天后突然连不上——而且那时候看日志只会说握手失败， 很难联想到证书。加一个续期后自动执行的钩子：\nsudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-xui.sh \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; #!/bin/sh systemctl restart x-ui EOF sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-xui.sh renewal-hooks/deploy/ 里的脚本只在证书真的续成功之后才执行， 不会每天白重启一次。\n小结 工具 作用 certbot 免费 HTTPS 证书 x-ui 可视化管理 Trojan / VLESS 等协议 nginx 只在 certbot --nginx 验证域名时用到，不参与代理 Let's Encrypt 提供 90 天有效期免费证书 这种搭建方式兼顾易用性、可视化和协议灵活性，适合科学上网初学者快速部署。\n","permalink":"https://www.yichengcode.com/posts/setup-trojan-server/","summary":"\u003cp\u003eTrojan 把代理流量伪装成正常的 HTTPS，所以它\u003cstrong\u003e必须有一张真实域名的证书\u003c/strong\u003e——\n这是它和免证书协议最大的区别，也是这篇一半篇幅在讲证书的原因。\u003c/p\u003e\n\u003cp\u003e本文环境：Ubuntu 20.04 / Debian 11，面板用 \u003ca href=\"https://github.com/mhsanaei/3x-ui\"\u003emhsanaei/3x-ui\u003c/a\u003e，\n示例域名 \u003ccode\u003eus.example.com\u003c/code\u003e。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"一安装证书工具\"\u003e一、安装证书工具\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt update\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt install nginx certbot python3-certbot-nginx -y\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003ecertbot\u003c/code\u003e 负责签发 Let\u0026rsquo;s Encrypt 证书，\u003ccode\u003epython3-certbot-nginx\u003c/code\u003e 是让它自动改\nNginx 配置的插件。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003enginx 也要装。\u003c/strong\u003e 下一步的 \u003ccode\u003ecertbot --nginx\u003c/code\u003e 是靠临时改 Nginx 配置来完成\n域名验证的，机器上没有 Nginx 这条命令会直接失败。装了之后 Nginx 只在签证书时用到，\n不参与代理。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"二为域名申请-https-证书\"\u003e二、为域名申请 HTTPS 证书\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo certbot --nginx -d us.example.com\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cul\u003e\n\u003cli\u003e请确保你的域名已经解析到当前 VPS 公网 IP。\u003c/li\u003e\n\u003cli\u003e请确保服务器的 80 和 443 端口已开放。部分 VPS 提供商要求不仅在系统内部（如通过 ufw 或 firewalld）放行端口，还需在云平台的 VPC 网络安全组 或 防火墙策略 中手动添加相应的入站规则，否则外部访问可能被阻断。\u003c/li\u003e\n\u003cli\u003e如果申请成功，证书和密钥默认保存在：\u003cbr\u003e\n\u003ccode\u003e/etc/letsencrypt/live/us.example.com/fullchain.pem\u003c/code\u003e\u003cbr\u003e\n\u003ccode\u003e/etc/letsencrypt/live/us.example.com/privkey.pem\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\u003c/blockquote\u003e\n\u003chr\u003e\n\u003ch2 id=\"三安装-3x-ui-面板\"\u003e三、安装 3x-ui 面板\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebash \u0026lt;\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ecurl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e安装完成后，运行：\u003c/p\u003e","title":"用 3x-ui 搭建 Trojan TLS 节点：证书申请、面板配置与自动续期"},{"content":"在进行 Tiktok/Facebook/Instagram营销、注册或自动化管理时，稳定的代理是不可或缺的。本文将介绍如何使用 gost 配合 Docker，快速部署一个带认证的 socks5 代理服务器。\n一、安装 Docker 在大多数 Linux 服务器上，只需一条命令即可安装 Docker：\napt update \u0026amp;\u0026amp; apt install -y docker.io 💡 如果你是 CentOS 或 Alpine 用户，请使用对应的安装方式。\n二、运行 Gost SOCKS5 容器 以下命令会拉取 Gost 镜像并运行一个代理容器：\ndocker run -d \\ --name gost-socks5 \\ -p 886:1080 \\ ginuerzh/gost \\ -L=socks5://proxyuser:CHANGE_ME_强密码@:1080 参数解释： -d：后台运行容器（detached 模式） --name gost-socks5：容器名称 -p 886:1080：把宿主机的 886 端口映射到容器内的 1080 端口。冒号左边是外面连的端口，右边是容器里 gost 实际监听的端口，两个数不需要一样 ginuerzh/gost：使用官方 Gost 镜像 -L=socks5://用户名:密码@:1080：监听本地 1080 端口，开启带用户名密码认证的 SOCKS5 服务 ⚠️ 请根据实际需求修改用户名、密码、端口（proxyuser 和那串密码都要换掉）。\n三、修改配置：删掉重建 gost 的账号密码和监听端口都是启动参数，不是配置文件，所以改不了——只能删掉容器重建。 容器本身不存数据，删了没有任何损失：\ndocker stop gost-socks5 \u0026amp;\u0026amp; docker rm gost-socks5 # 重新运行（比如更换端口或密码） docker run -d \\ --name gost-socks5 \\ -p 886:1080 \\ ginuerzh/gost \\ -L=socks5://proxyuser:CHANGE_ME_强密码@:1080 四、在自动化工具里使用 你可以在 TikTok 的注册、登录、自动浏览中，将代理地址设置为：\nsocks5://proxyuser:CHANGE_ME_强密码@你的服务器IP:886 支持的软件示例：\nPlaywright / Puppeteer 自动化 指纹浏览器 Clash / Proxifier / curl 五、安全建议 避免使用默认端口（如 1080），容易被扫描 尽量开启认证，防止被滥用 可用 UFW / iptables 设置 IP 白名单 实测可用VPS推荐 Vultr DigitalOcean 小结 通过 Docker 和 Gost，一条命令即可快速搭建一个稳定、可认证的 SOCKS5 代理，非常适合 TikTok 营销、社媒批量操作等场景。\n","permalink":"https://www.yichengcode.com/posts/setup-tk-server/","summary":"\u003cp\u003e在进行 Tiktok/Facebook/Instagram营销、注册或自动化管理时，稳定的代理是不可或缺的。本文将介绍如何使用 \u003ca href=\"https://github.com/ginuerzh/gost\"\u003egost\u003c/a\u003e 配合 Docker，快速部署一个带认证的 socks5 代理服务器。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"一安装-docker\"\u003e一、安装 Docker\u003c/h2\u003e\n\u003cp\u003e在大多数 Linux 服务器上，只需一条命令即可安装 Docker：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eapt update \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e apt install -y docker.io\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e💡 如果你是 CentOS 或 Alpine 用户，请使用对应的安装方式。\u003c/p\u003e\u003c/blockquote\u003e\n\u003chr\u003e\n\u003ch2 id=\"二运行-gost-socks5-容器\"\u003e二、运行 Gost SOCKS5 容器\u003c/h2\u003e\n\u003cp\u003e以下命令会拉取 Gost 镜像并运行一个代理容器：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker run -d \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e  --name gost-socks5 \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e  -p 886:1080 \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e  ginuerzh/gost \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e  -L\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003esocks5://proxyuser:CHANGE_ME_强密码@:1080\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"参数解释\"\u003e参数解释：\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003e-d\u003c/code\u003e：后台运行容器（detached 模式）\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e--name gost-socks5\u003c/code\u003e：容器名称\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e-p 886:1080\u003c/code\u003e：把宿主机的 886 端口映射到容器内的 1080 端口。\u003cstrong\u003e冒号左边是外面连的端口，右边是容器里 gost 实际监听的端口\u003c/strong\u003e，两个数不需要一样\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eginuerzh/gost\u003c/code\u003e：使用官方 Gost 镜像\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e-L=socks5://用户名:密码@:1080\u003c/code\u003e：监听本地 1080 端口，开启带用户名密码认证的 SOCKS5 服务\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e⚠️ 请根据实际需求修改用户名、密码、端口（\u003ccode\u003eproxyuser\u003c/code\u003e 和那串密码都要换掉）。\u003c/p\u003e","title":"使用 Docker 和 Gost 快速搭建 SOCKS5 代理（适用于 TikTok 自建代理）"},{"content":" 本文适用于：需要自建域名邮箱来注册平台账号、接收验证码的场景。全程命令行， 不用写 Python 脚本也能收码。\n只讲收信。 对外发信要额外处理 rDNS、DKIM、DMARC 和 IP 信誉， 自建服务器发出去的信大概率进垃圾箱，那是另一件事。\n一、背景说明 很多平台注册账号时会要求填写邮箱地址并接收验证码。为了批量注册或控制邮箱来源，一些用户希望自建域名邮箱系统，从而：\n自己控制域名和邮箱地址 本地接收邮件，免费无限用 配合脚本自动抽取验证码 二、工作环境准备 1. 需要的条件 项目 说明 一台 VPS Ubuntu 20.04+，25 端口必须能入站。多数云厂商默认封 25，要向主机商申请解封 一个可配置 DNS 的域名 如 mail.example.com 2. 安装必要软件 apt update apt install postfix dovecot-core dovecot-imapd -y postfix = SMTP 邮件服务器，用于接收邮件 dovecot = IMAP 邮箱读取服务器，允许脚本/网页读邮件 三、配置 DNS 记录 到域名控制面板（如 Cloudflare、阿里云、Namecheap）添加以下 DNS 记录:\n类型 主机记录 值 A mail VPS IP MX @ mail.example.com (10) TXT @ v=spf1 mx ~all 说明：MX 是接收邮件的域名服务器，SPF 是为了防止邮件被伪造，非必但建议配置\n四、配置 Postfix + Dovecot 配置 Postfix 编辑 /etc/postfix/main.cf ，加入或确保：\nmyhostname = mail.example.com mydestination = $myhostname, localhost.$mydomain, localhost, example.com home_mailbox = Maildir/ smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key smtpd_use_tls=yes inet_interfaces = all 几个关键项的含义：\n配置项 作用 写错的后果 myhostname 这台机器对外自称的名字 对方服务器可能拒收 mydestination 哪些域名的信算「本机的」并投递到本地 域名不在这个列表里，Postfix 会当成中转请求拒掉 home_mailbox = Maildir/ 用 Maildir 格式，一封信一个文件 默认是 mbox（整个邮箱一个大文件），后面按文件取信的命令全都用不了 inet_interfaces = all 监听所有网卡 默认只听 localhost，外面的信根本进不来 ssl-cert-snakeoil 是系统自带的自签证书，用于收信足够了；要用正式证书就换成 Let\u0026rsquo;s Encrypt 签发的路径。\n配置 Dovecot 编辑 /etc/dovecot/conf.d/10-mail.conf：\nmail_location = maildir:~/Maildir 这一行必须和 Postfix 的 home_mailbox 对上。 Postfix 按 Maildir 格式写信、 Dovecot 却按 mbox 格式找，结果就是「信收到了，但客户端里一封都看不到」—— 两边都不报错，最难查的就是这种。\n五、创建邮箱用户 每个 Linux 用户 = 一个邮箱地址\nadduser code # 即创建 code@example.com 重启服务：\nsystemctl restart postfix dovecot 六、测试接收 用你的 Gmail/QQ/163，给 code@example.com 发一封邮件 在服务器上查看 ls /home/code/Maildir/new cat /home/code/Maildir/new/文件名 接收成功，表明邮箱配置已成功 七、命令行接收验证码 提取最新邮件: cd /home/code/Maildir/new latest=$(ls -t | head -n1) cat \u0026#34;$latest\u0026#34; 只看正文: sed -n \u0026#39;/^$/,$p\u0026#39; \u0026#34;$latest\u0026#34; 提取6位数验证码: sed -n \u0026#39;/^$/,$p\u0026#39; \u0026#34;$latest\u0026#34; | grep -oE \u0026#39;\\b[0-9]{6}\\b\u0026#39; 八、收不到信时按这个顺序查 现象 先查什么 发过去直接退信 MX 记录解析对不对：dig MX example.com 没退信也没收到 25 端口通不通：从别的机器 telnet 你的IP 25 Postfix 收到了但目录是空的 mail_location 和 home_mailbox 格式是否一致 都正常但就是没有 tail -f /var/log/mail.log，Postfix 的日志写得很清楚 小结 环节 用什么 收信 Postfix（SMTP） 读信 Dovecot（IMAP） 存储格式 Maildir，一封信一个文件，落在 /home/用户名/Maildir/new/ DNS A + MX + SPF 抽验证码 sed 取正文 + grep -oE 匹配 6 位数字 走完这一遍就有了一套自己可控的收信系统：接验证码、配合脚本自动注册、把抽到的码 推给 API 都可以。\n一台服务器要接多个域名的话，不再靠系统用户，改用虚拟邮箱——参考 Postfix 官方的 VIRTUAL_README。\n附录：用到的 Shell 命令 本节整理了一些在邮件接收流程中用到的常见命令及参数说明，方便读者理解并活用。\n命令 示例 说明 ls -t ls -t | head -n1 -t 表示按修改时间排序，配合 head -n1 获取最新邮件文件名 head -n1 head -n1 获取输出的第一行（如最新文件名） cat cat 文件名 查看文件全部内容，包括邮件头和正文 sed -n '/^$/,$p' sed -n '/^$/,$p' \u0026quot;$latest\u0026quot; sed -n 不自动打印；/^$/,$ 表示从空行开始到文件结尾；p 表示打印匹配的部分；用于提取邮件正文 grep -oE '\\b[0-9]{6}\\b' grep -oE '\\b[0-9]{6}\\b' -o 只输出匹配内容，-E 使用扩展正则；\\b[0-9]{6}\\b 匹配完整的 6 位数字验证码 adduser 用户名 adduser code 创建 Linux 系统用户，对应邮箱 code@example.com ","permalink":"https://www.yichengcode.com/posts/setup-mail-server/","summary":"\u003cblockquote\u003e\n\u003cp\u003e本文适用于：需要自建域名邮箱来注册平台账号、接收验证码的场景。全程命令行，\n不用写 Python 脚本也能收码。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e只讲收信。\u003c/strong\u003e 对外发信要额外处理 rDNS、DKIM、DMARC 和 IP 信誉，\n自建服务器发出去的信大概率进垃圾箱，那是另一件事。\u003c/p\u003e\u003c/blockquote\u003e\n\u003chr\u003e\n\u003ch2 id=\"一背景说明\"\u003e一、背景说明\u003c/h2\u003e\n\u003cp\u003e很多平台注册账号时会要求填写邮箱地址并接收验证码。为了批量注册或控制邮箱来源，一些用户希望自建域名邮箱系统，从而：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e自己控制域名和邮箱地址\u003c/li\u003e\n\u003cli\u003e本地接收邮件，免费无限用\u003c/li\u003e\n\u003cli\u003e配合脚本自动抽取验证码\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"二工作环境准备\"\u003e二、工作环境准备\u003c/h2\u003e\n\u003ch3 id=\"1-需要的条件\"\u003e1. 需要的条件\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e项目\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e一台 VPS\u003c/td\u003e\n          \u003ctd\u003eUbuntu 20.04+，\u003cstrong\u003e25 端口必须能入站\u003c/strong\u003e。多数云厂商默认封 25，要向主机商申请解封\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e一个可配置 DNS 的域名\u003c/td\u003e\n          \u003ctd\u003e如 mail.example.com\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch3 id=\"2-安装必要软件\"\u003e2. 安装必要软件\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eapt update\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eapt install postfix dovecot-core dovecot-imapd -y\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003epostfix\u003c/code\u003e = SMTP 邮件服务器，用于接收邮件\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003edovecot\u003c/code\u003e = IMAP 邮箱读取服务器，允许脚本/网页读邮件\u003c/li\u003e\n\u003c/ul\u003e\u003c/blockquote\u003e\n\u003chr\u003e\n\u003ch2 id=\"三配置-dns-记录\"\u003e三、配置 DNS 记录\u003c/h2\u003e\n\u003cp\u003e到域名控制面板（如 Cloudflare、阿里云、Namecheap）添加以下 DNS 记录:\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e类型\u003c/th\u003e\n          \u003cth\u003e主机记录\u003c/th\u003e\n          \u003cth\u003e值\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eA\u003c/td\u003e\n          \u003ctd\u003email\u003c/td\u003e\n          \u003ctd\u003eVPS IP\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eMX\u003c/td\u003e\n          \u003ctd\u003e@\u003c/td\u003e\n          \u003ctd\u003email.example.com (10)\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eTXT\u003c/td\u003e\n          \u003ctd\u003e@\u003c/td\u003e\n          \u003ctd\u003ev=spf1 mx ~all\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cblockquote\u003e\n\u003cp\u003e说明：MX 是接收邮件的域名服务器，SPF 是为了防止邮件被伪造，非必但建议配置\u003c/p\u003e","title":"从零搭建邮件服务器接收验证码"},{"content":"你好，我是一个写代码的人。\n也是那个把琐碎流程变成静默脚本，把复杂系统变得井然有序的人。\n我热爱这个世界里所有“可被自动化的细节”，\n它们或许不起眼，却能构成一台效率的机器。\n偶尔调调模型，和服务器长谈片刻；\n也会盯着终端发呆，想让每一行指令都像诗一样简洁有力。\n我始终相信：\n人不该困在机械重复的环节里，脚本才是最好的接班人。\n🧩 目前我正做的事，都是让“事情自己运转” 把铺货做成一个按钮，点一下，几千个商品安静落地 把账号养成托付给浏览器，每天两百个分身准时上班 把数据采集设成定时呼吸，悄无声息却有条不紊 把客户回复交给 AI，语气温柔，态度永不疲倦 我调大模型，也被模型调。\n在代码与模型之间，我建起桥梁，也搭了房子。\n🧰 我的工具箱里，藏着一个操作系统的灵魂： 后端炼金术：Laravel / Flask / FastAPI / Spring Boot 前端幻术：Next.js / React Native / Tailwind CSS 数据炼妖壶：MongoDB / MySQL / Redis 容器召唤术：Docker / Shell / Nginx / Git 自动化三剑客：Playwright / Scrapy / Selenium AI 精灵球：Transformers / LangChain / 微调 / Ollama / RAG 代理黑科技：gost、clash、trojan、中转机、落地机、带壳 CDN 账号工厂流水线：脚本注册、容器隔离、指纹伪装、IP 轮转，一气呵成 💡 我一直相信： 代码不是命令，是意志的延伸。\n自动化不是逃避，是信任系统的一种表达。\nAI，不只是工具，更是我在数字世界里的战友。\n而我，愿意做一个用脚本种花、用系统造林的数字种植者。\n📫 找我聊聊？ 微信：bian003\n如果你也在构建属于自己的系统，\n如果你也在尝试把混乱变得有序，\n如果你也在用工具拥抱自由——\n那我们，也许正好能聊一聊。\n因为这个世界，总有一些灵魂，不愿平庸，也不肯停下。\n","permalink":"https://www.yichengcode.com/about/","summary":"\u003cp\u003e你好，我是一个写代码的人。\u003cbr\u003e\n也是那个把琐碎流程变成静默脚本，把复杂系统变得井然有序的人。\u003c/p\u003e\n\u003cp\u003e我热爱这个世界里所有“可被自动化的细节”，\u003cbr\u003e\n它们或许不起眼，却能构成一台效率的机器。\u003cbr\u003e\n偶尔调调模型，和服务器长谈片刻；\u003cbr\u003e\n也会盯着终端发呆，想让每一行指令都像诗一样简洁有力。\u003c/p\u003e\n\u003cp\u003e我始终相信：\u003cbr\u003e\n\u003cstrong\u003e人不该困在机械重复的环节里，脚本才是最好的接班人。\u003c/strong\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"-目前我正做的事都是让事情自己运转\"\u003e🧩 目前我正做的事，都是让“事情自己运转”\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e把\u003cstrong\u003e铺货\u003c/strong\u003e做成一个按钮，点一下，几千个商品安静落地\u003c/li\u003e\n\u003cli\u003e把\u003cstrong\u003e账号养成\u003c/strong\u003e托付给浏览器，每天两百个分身准时上班\u003c/li\u003e\n\u003cli\u003e把\u003cstrong\u003e数据采集\u003c/strong\u003e设成定时呼吸，悄无声息却有条不紊\u003c/li\u003e\n\u003cli\u003e把\u003cstrong\u003e客户回复\u003c/strong\u003e交给 AI，语气温柔，态度永不疲倦\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e我调大模型，也被模型调。\u003cbr\u003e\n在代码与模型之间，我建起桥梁，也搭了房子。\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"-我的工具箱里藏着一个操作系统的灵魂\"\u003e🧰 我的工具箱里，藏着一个操作系统的灵魂：\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e后端炼金术\u003c/strong\u003e：Laravel / Flask / FastAPI / Spring Boot\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e前端幻术\u003c/strong\u003e：Next.js / React Native / Tailwind CSS\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e数据炼妖壶\u003c/strong\u003e：MongoDB / MySQL / Redis\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e容器召唤术\u003c/strong\u003e：Docker / Shell / Nginx / Git\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e自动化三剑客\u003c/strong\u003e：Playwright / Scrapy / Selenium\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAI 精灵球\u003c/strong\u003e：Transformers / LangChain / 微调 / Ollama / RAG\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e代理黑科技\u003c/strong\u003e：gost、clash、trojan、中转机、落地机、带壳 CDN\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e账号工厂流水线\u003c/strong\u003e：脚本注册、容器隔离、指纹伪装、IP 轮转，一气呵成\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch3 id=\"-我一直相信\"\u003e💡 我一直相信：\u003c/h3\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e代码不是命令，是意志的延伸。\u003c/strong\u003e\u003cbr\u003e\n\u003cstrong\u003e自动化不是逃避，是信任系统的一种表达。\u003c/strong\u003e\u003cbr\u003e\n\u003cstrong\u003eAI，不只是工具，更是我在数字世界里的战友。\u003c/strong\u003e\u003cbr\u003e\n而我，愿意做一个用脚本种花、用系统造林的数字种植者。\u003c/p\u003e","title":"关于我"}]