读:Emacs 连接数据库时密码放哪里
目录
引子
方案一:pgpass 文件 + 解析函数
Stefano Rodighiero 的做法:把连接信息放在 ~/.pgpass 文件里(PostgreSQL 客户端库 libpq 默认读取的密码文件),用解析函数把它转成 sql-connection-alist (sql-mode 存储连接配置的变量)。
文件格式
Stefano 在标准 pgpass 格式( hostname:port:database:username:password )前面加了一行注释作为连接名:
# mydb db.example.com:5432:mydb:myuser:mypassword # analytics analytics.example.com:5432:analytics:analyst:secretpass
标准 pgpass 本身支持 # 开头的注释行,所以这个文件同时兼容 PostgreSQL 客户端工具和 Stefano 的解析函数。
解析函数
(defun get-connection-alist (filename) "从 FILENAME (pgpass 格式) 解析出 sql-connection-alist." (with-current-buffer (find-file-noselect filename) (let ((lines (split-string (buffer-string) "\n" t))) (when lines (cl-loop for (k v) in (seq-partition lines 2) collect (cl-destructuring-bind (host port db user password) (split-string v ":" nil) `(,(replace-regexp-in-string "^#\s+" "" k) (sql-product 'postgres) (sql-port ,(string-to-number port)) (sql-server ,host) (sql-user ,user) (sql-database ,db) (sql-password ,password))))))))
函数做的事:打开文件,按行分割,每两行为一组(注释行是名称,数据行是连接信息),解析数据行的冒号分隔字段,组装成 sql-connection-alist 需要的格式。
设置和自动重载
(setq sql-connection-alist (get-connection-alist "~/.pgpass"))
Stefano 用 Emacs daemon 模式(Emacs 后台常驻,客户端连上去用),Emacs 一跑可能就是几周。期间 pgpass 文件可能改了(比如换了密码、加了新库),他用 file-notify (Emacs 内置的文件变更监控)自动重载:
(file-notify-add-watch "~/.pgpass" '(change) (lambda (evt) (setq sql-connection-alist (get-connection-alist "~/.pgpass"))))
pgpass 文件一改, sql-connection-alist 自动更新,不用重启 Emacs。
特点
- 连接信息和密码都在一个文件里,格式简单
- 密码在内存中是明文(存在
sql-connection-alist里) - pgpass 文件本身也是明文,需要注意文件权限(建议
chmod 600) - 主要适合 PostgreSQL 系数据库
方案二:authinfo.gpg + auth-source
Harald J 的做法:密码存在 ~/.authinfo.gpg 里(GPG 加密的 authinfo 文件),sql-mode 需要密码时通过 auth-source 查找。auth-source 是 Emacs 内置的统一密码存储接口,支持 netrc 文件、GPG 加密文件、系统密钥环等后端。连接定义里不出现密码。
authinfo 文件格式
machine db.example.com/mydb login myuser password mypassword port 5432 machine analytics.example.com/analytics login analyst password secretpass port 5432
machine 字段用 host/dbname 格式(主机名和数据库名用斜杠拼接),用来区分同一主机上的不同数据库。这个文件用 GPG 加密后存为 ~/.authinfo.gpg 。
自定义密码查找函数
(defun my/sql-auth-source-search-wallet (wallet product user server database port) "从 auth-source WALLET 中查找 USER 的密码。 DATABASE 和 SERVER 用斜杠拼接作为 host 查找键。" (when-let (results (auth-source-search :host (concat server "/" database) :user user :port (number-to-string port))) (when (and (= (length results) 1) (plist-member (car results) :secret)) (let ((secret (plist-get (car results) :secret))) ;; Emacs 26+ 的 auth-source 返回 :secret 为函数,需要 funcall (if (functionp secret) (funcall secret) secret))))))
函数做的事:接收连接参数(主机、数据库、用户、端口),把主机名和数据库名拼成 host/dbname 作为查找键,从 authinfo 文件里找到对应条目。 auth-source-search 是 auth-source 提供的查找函数。
配置 sql-mode
(setq sql-password-search-wallet-function #'my/sql-auth-source-search-wallet) (setq sql-password-wallet "~/.authinfo.gpg")
sql-password-search-wallet-function 告诉 sql-mode "需要密码时调这个函数", sql-password-wallet 指定密码存储文件的位置。
连接定义里不写密码
(setq sql-connection-alist '((mydb (sql-product 'postgres) (sql-port 5432) (sql-server "db.example.com") (sql-user "myuser") (sql-database "mydb")) (analytics (sql-product 'postgres) (sql-port 5432) (sql-server "analytics.example.com") (sql-user "analyst") (sql-database "analytics"))))
连接定义里只有服务器、端口、用户名,没有密码。sql-mode 实际连接数据库时才从 authinfo.gpg 里查找。
特点
- 密码存在 GPG 加密文件里,安全性好
- 复用 Emacs 内置的 auth-source,和 TRAMP、ERC 等其他需要密码的场景共享同一套存储
- 连接定义和密码存储分离,
sql-connection-alist里不出现明文密码 - 需要配置 GPG 环境(gpg-agent)
- 不限于 PostgreSQL,适合所有数据库类型
在 Org Babel 里使用
配好 sql-connection-alist 之后,在 Org Babel 的 SQL 代码块里通过 :dbconnection 参数引用连接名:
#+begin_src sql :product postgres :dbconnection mydb :results raw SELECT current_database(), current_user; #+end_src
:dbconnection mydb 让 Org Babel 用 sql-connection-alist 里名为 mydb 的连接去执行 SQL。Org 文件里不出现密码。
方案二在 Org Babel 里有个限制。 ob-sql (Org Babel 的 SQL 执行模块)直接从 sql-connection-alist 读取密码,不走 sql-password-search-wallet-function 。方案二的连接定义里没有密码,所以 ob-sql 执行时会因为缺少密码而认证失败。
解决办法有两种:
- 在初始化时从 authinfo 读取密码填入
sql-connection-alist(但这会把密码放进内存,削弱了方案二的分离优势) - 同时配置
~/.pgpass文件,让 psql 自己找密码(psql 原生支持读取~/.pgpass)
方案一没有这个问题,因为密码本来就在 sql-connection-alist 里。
对比
| 维度 | pgpass 方案 | authinfo.gpg 方案 |
|---|---|---|
| 密码存储 | 明文文件(需注意文件权限) | GPG 加密文件 |
| 配置复杂度 | 低(一个文件 + 一个解析函数) | 中(需要 GPG 环境 + 自定义查找函数) |
| 密码在内存中 | 明文存在 sql-connection-alist 里 | 按需从 authinfo.gpg 读取 |
| 数据库类型 | 主要适合 PostgreSQL 系 | 适合所有类型 |
| Org Babel 支持 | 直接可用 | 需要额外处理(ob-sql 不走 wallet 函数) |
| 与 Emacs 生态集成 | 独立方案 | 复用 auth-source,可与其他包共享密码 |
| 自动重载 | file-notify 监控文件变更 | auth-source 自行管理缓存 |
只用 PostgreSQL 且对安全性要求不高,pgpass 方案简单直接,Org Babel 也开箱即用。用多种数据库或已经在用 authinfo.gpg 管理其他密码,authinfo 方案更统一,但用在 Org Babel 里需要额外配置。