Libravatar 2.0 常用 CLI 命令与工具详解
运维 ivatar 2.0 的命令手册:manage.py 命令族、systemd/nginx 服务管理、curl 全参数头像 API 验证与 dig 验证 DNS SRV 联邦记录,全部命令与回显来自真实部署实例。
Libravatar 2.0 常用 CLI 命令与工具详解
目标:把运维 Libravatar 服务端(ivatar 2.0)每天要碰的命令一次讲全——manage.py 命令族(含 django-extensions 扩展)、systemd/nginx 服务管理、curl 构造哈希头像 URL 的全参数验证、dig 验证 DNS SRV 联邦记录。全部命令与回显来自真实部署实例(部署过程见《Ubuntu 22.04 安装 Libravatar 2.0 指南》),逐行照抄。
一、环境信息
| 项目 | 取值 |
|---|---|
| 操作系统 | Ubuntu 22.04.5 LTS(x86_64) |
| 服务端 | ivatar 2.0(commit 6f4b7e0),部署于 /opt/ivatar |
| Python / Django | 3.10.12 / 5.2.18(虚拟环境 /opt/ivatar/.virtualenv) |
| 数据库 | PostgreSQL 14.24,库 ivatar,用户 ivatar_user |
| 应用/反代/缓存 | gunicorn 26.2.0(127.0.0.1:8000)/ nginx 1.18.0(8080)/ memcached 1.6.14 |
| 演示数据 | 管理员 admin@example.com,演示邮箱 alice@example.com、bob@example.com(各有一张照片) |
二、命令体系总览
ivatar 的命令面分四族,各自入口不同:
| 命令族 | 入口 | 用途 |
|---|---|---|
| manage.py(Django 内置) | cd /opt/ivatar && .virtualenv/bin/python manage.py <命令> | 数据库迁移、静态文件、管理员、shell/dbshell |
| manage.py(django-extensions 扩展) | 同上 | generate_secret_key、clear_cache、show_urls 等开发运维增强 |
| 服务管理 | systemctl / nginx -t | ivatar.service、nginx、postgresql、memcached 生命周期 |
| 验证工具 | curl / dig | 头像 API 行为验证、DNS SRV 联邦核查 |
铁律:manage.py 必须在 /opt/ivatar 目录下用虚拟环境的 python 执行,在其他目录执行会报 manage.py 文件不存在;每条 manage.py 输出第一行的 No metric readers configured - metrics will not be exported 是 OpenTelemetry 未配导出器的正常提示(详见配置篇排障 5.5),本文回显均已略去该行。
三、manage.py 命令族
3.1 全量命令清单
manage.py help 输出(按应用分组,88 行照抄):
# cd /opt/ivatar && .virtualenv/bin/python manage.py help
gunicorn (version 26.2.0)
Type 'manage.py help <subcommand>' for help on a specific subcommand.
Available subcommands:
[auth]
changepassword
createsuperuser
[contenttypes]
remove_stale_contenttypes
[django]
check
compilemessages
createcachetable
dbshell
diffsettings
dumpdata
flush
inspectdb
loaddata
makemessages
makemigrations
migrate
optimizemigration
sendtestemail
shell
showmigrations
sqlflush
sqlmigrate
sqlsequencereset
squashmigrations
startapp
startproject
test
testserver
[django_extensions]
admin_generator
clean_pyc
clear_cache
compile_pyc
create_command
create_jobs
create_template_tags
delete_squashed_migrations
describe_form
drop_test_database
dumpscript
export_emails
find_template
generate_password
generate_secret_key
graph_models
list_model_info
list_signals
mail_debug
managestate
merge_model_instances
notes
print_settings
print_user_for_session
raise_test_exception
reset_db
reset_schema
runjob
runjobs
runprofileserver
runscript
runserver_plus
set_default_site
set_fake_emails
set_fake_passwords
shell_plus
show_permissions
show_template_tags
show_urls
sqlcreate
sqldiff
sqldsn
sync_s3
syncdata
unreferenced_files
update_permissions
validate_templates
[django_openid_auth]
openid_cleanup
[sessions]
clearsessions
[social_django]
clearsocial
[staticfiles]
collectstatic
findstatic
runserver
首行 gunicorn (version 26.2.0) 是 manage.py 启动横幅(django-extensions 注入),顺带确认了 WSGI 服务器版本。日常运维最高频的 8 个命令:generate_secret_key、migrate、showmigrations、collectstatic、createsuperuser、clear_cache、diffsettings、dbshell。
3.2 generate_secret_key——生成 SECRET_KEY
# .virtualenv/bin/python manage.py help generate_secret_key
usage: manage.py generate_secret_key [-h] [--version] [-v {0,1,2,3}]
[--settings SETTINGS]
[--pythonpath PYTHONPATH] [--traceback]
[--no-color] [--force-color]
Generates a new SECRET_KEY that can be used in a project settings file.
options:
-h, --help show this help message and exit
--version Show program's version number and exit.
-v {0,1,2,3}, --verbosity {0,1,2,3}
Verbosity level; 0=minimal output, 1=normal output,
2=verbose output, 3=very verbose output
--settings SETTINGS The Python path to a settings module, e.g.
"myproject.settings.main". If this isn't provided, the
DJANGO_SETTINGS_MODULE environment variable will be
used.
--pythonpath PYTHONPATH
A directory to add to the Python path, e.g.
"/home/djangoprojects/myproject".
--traceback Display a full stack trace on CommandError exceptions.
--no-color Don't colorize the command output.
--force-color Force colorization of the command output.
# .virtualenv/bin/python manage.py generate_secret_key
o7$7dks(lldewtg9pz-cfip0a$$eq+7t#x8e2nkjjdu5ayk#=u
把输出整行粘进 config_local.py 的 SECRET_KEY = "…"。注意每次运行输出都不同(随机),已上线的实例不要随意重新生成——旧会话/重置链接会全部失效。
3.3 migrate / showmigrations——数据库迁移
migrate 的首次全量输出见安装篇 4.6(68 个迁移全部 OK)。增量核查用 showmigrations:
# .virtualenv/bin/python manage.py showmigrations
admin
[X] 0001_initial
(…略…)
auth
[X] 0001_initial
(…略…)
contenttypes
[X] 0001_initial
django_openid_auth
[X] 0001_initial
(…略…)
ivataraccount
[X] 0001_initial
[X] 0002_openidassociation_openidnonce
[X] 0003_auto_20180508_0637
(…略…)
[X] 0025_access_stat_tables
[X] 0026_confirmedemail_idx_cemail_bluesky_handle
sessions
[X] 0001_initial
social_django
[X] 0001_initial
(…略…)
# .virtualenv/bin/python manage.py showmigrations | grep -c "\[X\]"
69
[X] 表示已应用;69 个迁移全部落库。查看单个应用:manage.py showmigrations ivataraccount。
3.4 collectstatic / findstatic——静态文件
collectstatic 首次执行输出 1264 static files copied to '/opt/ivatar/static'.(安装篇 4.7)。排查"某个静态文件从哪来"用 findstatic:
# .virtualenv/bin/python manage.py findstatic css/bootstrap.min.css
Found 'css/bootstrap.min.css' here:
/opt/ivatar/ivatar/static/css/bootstrap.min.css
注意 findstatic 显示的是源文件位置(应用内的 static/ 目录),生产上对外服务的是 collectstatic 汇总后的 /opt/ivatar/static/(nginx alias 指向它)。
3.5 createsuperuser——创建管理员
# DJANGO_SUPERUSER_PASSWORD=q1w2e3r4 .virtualenv/bin/python manage.py createsuperuser --noinput --username admin --email admin@example.com
Superuser created successfully.
--noinput 配合环境变量免交互,适合脚本化初始化。
3.6 clear_cache——清空缓存
# .virtualenv/bin/python manage.py clear_cache
Cache "default" has been cleared!
改配置做行为验证前先清缓存(头像响应缓存 TTL 默认 7 天,见配置篇 3.3),否则可能读到旧响应。
3.7 diffsettings——核对生效配置
# .virtualenv/bin/python manage.py diffsettings | grep -E "^(SECURE_BASE_URL|DEFAULT_GRAVATARPROXY)"
BASE_URL = 'http://avatars.example.com/avatar/' ###
DEFAULT_GRAVATARPROXY = False ###
SECURE_BASE_URL = 'https://avatars.example.com/avatar/' ###
行尾 ### 是 diffsettings 的"与 Django 默认不同"标记。它是"我的覆盖到底生效没有"的唯一权威答案(完整用法见配置篇 2.2)。
3.8 show_urls——全站路由表
# .virtualenv/bin/python manage.py show_urls | grep -vE "^/admin|^/accounts|^/auth"
/ django.views.generic.base.TemplateView home
/avatar/ ivatar.views.AvatarImageView avatar_view
/avatar/<digest> django.views.generic.base.RedirectView invalid_hash
/avatar/<digest> ivatar.views.AvatarImageView avatar_view
/avatar/<digest> ivatar.views.AvatarImageView avatar_view
/blueskyproxy/<digest> ivatar.views.BlueskyProxyView blueskyproxy
/contact/ django.views.generic.base.TemplateView contact
/deployment/version/ ivatar.views.DeploymentVersionView deployment_version
/description/ django.views.generic.base.TemplateView description
/features/ django.views.generic.base.TemplateView features
/gravatarproxy/<digest> ivatar.views.GravatarProxyView gravatarproxy
/i18n/setlang/ django.views.i18n.set_language set_language
/openid/complete/ django_openid_auth.views.login_complete openid-complete
/openid/login/ django_openid_auth.views.login_begin openid-login
/openid/logo.gif django_openid_auth.views.logo openid-logo
/perf-test/ ivatar.views.PerfTestView perf_test
/privacy/ django.views.generic.base.TemplateView privacy
/run_your_own/ django.views.generic.base.TemplateView run_your_own
/security/ django.views.generic.base.TemplateView security
/stats/ ivatar.views.StatsView stats
/talk_to_us/ django.views.generic.base.RedirectView talk_to_us
/tools/check/ ivatar.tools.views.CheckView tools_check
/tools/check_domain ivatar.tools.views.CheckDomainView tools_check_domain
/tools/check_domain/ ivatar.tools.views.CheckDomainView tools_check_domain
/version/ ivatar.views.DeploymentVersionView version
(完整输出含 /admin/ 下约 150 条管理路由与 /accounts/ 下 40 余条账户路由,已略。)三条 /avatar/<digest> 分别匹配 32 位 MD5、64 位 SHA256 与非法哈希(非法的走 invalid_hash 重定向)。
3.9 dbshell——直连数据库
# .virtualenv/bin/python manage.py dbshell
(进入 psql,表清单节选)
List of relations
Schema | Name | Type | Owner
--------+-----------------------------------------+-------+-------------
public | auth_group | table | ivatar_user
public | auth_user | table | ivatar_user
public | django_migrations | table | ivatar_user
public | ivataraccount_confirmedemail | table | ivatar_user
public | ivataraccount_photo | table | ivatar_user
(其余略)
业务查询示例(照片表):
ivatar=# SELECT id, format, length(data) AS bytes FROM ivataraccount_photo ORDER BY id;
id | format | bytes
----+--------+-------
1 | png | 760
2 | png | 761
3 | png | 760
(3 rows)
坑:照片表没有 digest 列(digest 在 confirmedemail/openid 表上,由 GeneratedField 持久化)。想当然地 SELECT digest FROM ivataraccount_photo 会报:
ERROR: column "digest" does not exist
LINE 1: SELECT id, digest, size, format FROM ivataraccount_photo ORD…
先查列名再写 SQL:SELECT column_name FROM information_schema.columns WHERE table_name = 'ivataraccount_photo';(实测列为 id / add_date / ip_address / data / format / user_id)。
3.10 shell——ORM 交互
# echo "from ivatar.ivataraccount.models import Photo; print(Photo.objects.count())" | .virtualenv/bin/python manage.py shell
3
坑:应用模块路径是 ivatar.ivataraccount.models(两级包),写成 ivataraccount.models 会报 ModuleNotFoundError。批量造数、数据订正、导出脚本都走这个入口(exit 用 Ctrl-D 或 \q 不行——shell 是 Python REPL,用 exit())。
四、服务管理命令(systemd / nginx)
4.1 ivatar.service 生命周期
# systemctl enable --now ivatar
Created symlink /etc/systemd/system/multi-user.target.wants/ivatar.service → /etc/systemd/system/ivatar.service.
# systemctl status ivatar --no-pager | head -5
● ivatar.service - ivatar (Libravatar server) WSGI service
Loaded: loaded (/etc/systemd/system/ivatar.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2026-10-28 13:59:54 UTC; 2s ago
Main PID: 7636 (gunicorn)
Tasks: 5 (limit: 2932)
# systemctl restart ivatar # 改完 config_local.py 必做
改配置不重启 = 不生效(gunicorn 不热加载),这是最常见的一类"配置失效"假故障。
4.2 日志与排障
# journalctl -u ivatar --no-pager -n 4
Oct 28 13:52:07 ivatar-server gunicorn[8794]: File "/usr/lib/python3.10/urllib/request.py", line 1351, in do_open
Oct 28 13:52:07 ivatar-server gunicorn[8794]: raise URLError(err)
Oct 28 13:52:07 ivatar-server gunicorn[8794]: urllib.error.URLError: <urlopen error [Errno 101] Network is unreachable>
(这段真实日志来自开启 Gravatar 代理后无外网出口的场景,处理见配置篇排障 5.4。)应用日志另见 /opt/ivatar/logs/ivatar.log 与 /var/log/ivatar/nginx-*.log。
4.3 nginx 与依赖服务
# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# systemctl is-active ivatar nginx postgresql memcached
active
active
active
active
四服务全 active 是健康基线;任何一个 inactive,先 systemctl start 再看 journalctl。
五、curl 构造哈希 URL 验证 API
5.1 计算邮箱哈希
协议规则:邮箱转小写 → MD5(32 位)或 SHA256(64 位)→ 拼 /avatar/<hash>。命令行核实:
$ printf admin@example.com | tr A-Z a-z | md5sum | cut -d" " -f1
e64c7d89f26bd1972efa854d13d7dd61
$ printf admin@example.com | tr A-Z a-z | sha256sum | cut -d" " -f1
258d8dc916db8cea2cafb6c3cd0cb0246efe061421dbd83ec3a350428cabda4f
(管理员 admin@example.com 在本实例有照片,适合做正例;nobody@libravatar.org 的 md5 为 cbcfb6c818d647d0ebd8395ccfa0c2e5,适合做未命中反例。)
5.2 全参数场景实测
以下用本实例逐条实测(HTTP 200/302/404 + 类型 + 字节数):
| URL(相对 8080 端口) | 实测结果 | 说明 |
|---|---|---|
/avatar/e64c7d89f26bd1972efa854d13d7dd61 | 200 image/png 177B | 有头像默认 80px |
/avatar/e64c…dd61?s=1 | 200 … 69B | 最小 1px |
/avatar/e64c…dd61?s=512 | 200 … 1880B | 上限 512 |
/avatar/e64c…dd61?s=600 | 200 … 1880B | 超限被钳制到 512 |
/avatar/258d8dc9…abda4f(SHA256) | 200 image/png 177B | 与 MD5 结果一致 |
/avatar/cbcfb6c8…a0c2e5?d=identicon | 200 image/png 889B | 本地生成 identicon |
/avatar/cbcfb6c8…a0c2e5?d=retro | 200 image/png 1267B | 本地生成复古像素 |
/avatar/cbcfb6c8…a0c2e5?d=mm | 302 -> /static/img/mm/80.png | 灰色人形剪影 |
/avatar/cbcfb6c8…a0c2e5?d=404 | 404 text/html | 显式 404 |
/avatar/e64c…dd61?f=y | 302 -> /static/img/nobody/80.png | 强制默认(有头像也忽略) |
/avatar/deadbeef | 302 -> /static/img/deadbeef.png | 非法哈希固定重定向 |
/avatar/c160f8cc…53d060 + Accept: image/webp | 200 image/webp 96B | WebP 内容协商(用户照片路径) |
302 与 404 场景的完整响应头(Location 是判据):
$ curl -si "http://avatars.example.com:8080/avatar/cbcfb6c818d647d0ebd8395ccfa0c2e5?d=mm" | head -8
HTTP/1.1 302 Found
Server: nginx/1.18.0 (Ubuntu)
Date: Wed, 28 Oct 2026 13:41:19 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Connection: keep-alive
Location: /static/img/mm/80.png
Etag: "cbcfb6c818d647d0ebd8395ccfa0c2e5"
$ curl -si "http://avatars.example.com:8080/avatar/e64c7d89f26bd1972efa854d13d7dd61?f=y" | head -8
HTTP/1.1 302 Found
…
Location: /static/img/nobody/80.png
Etag: "e64c7d89f26bd1972efa854d13d7dd61"
$ curl -si "http://avatars.example.com:8080/avatar/deadbeef" | head -8
HTTP/1.1 302 Found
…
Location: /static/img/deadbeef.png
Etag: "deadbeef"
命中用户照片时的 200 响应头(注意 Vary: Accept——WebP 协商的缓存分键):
$ curl -sI "http://avatars.example.com:8080/avatar/e64c7d89f26bd1972efa854d13d7dd61"
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Content-Type: image/png
Content-Length: 177
Vary: Accept
Etag: "e64c7d89f26bd1972efa854d13d7dd61"
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
坑:验证开关类参数(WebP、d= 变化)时,同一哈希的旧响应会被缓存(7 天 TTL)骗到你怀疑人生——换全新邮箱哈希测,或先
manage.py clear_cache。
5.3 version 与 stats 端点
$ curl -s "http://avatars.example.com:8080/version/"
{"commit_hash": "6f4b7e01cb2a6be57fea63aeb9fc95414b2d2037", "short_hash": "6f4b7e0", "branch": "master", "commit_date": "2026-10-28 13:13:16 ", "deployment_date": "2026-10-28 13:13:16 ", "deployment_status": "active", "version": "master-6f4b7e0", "application_version": "2.0"}
$ curl -s "http://avatars.example.com:8080/stats/" | python3 -m json.tool | head -8
{
"users": 1,
"mails": 3,
"openids": 0,
"unconfirmed_mails": 0,
(后略:top_viewed_avatars / photo_format_distribution / cache_stats 等)
application_version: "2.0" 即 config.py 的 IVATAR_VERSION;/stats/ 给出用户数、照片格式分布与各类缓存水位(缓存 TTL 见配置篇 3.3)。
六、dig 验证 DNS SRV 联邦记录
联邦机制靠两条 SRV 记录把邮箱域名委派到头像服务器(原理见选型篇三章)。命令行核查官方域名:
$ dig +short SRV _avatars._tcp.libravatar.org
0 0 80 cdn.libravatar.org.
$ dig +short SRV _avatars-sec._tcp.libravatar.org
0 0 443 seccdn.libravatar.org.
带完整头部的回答段(可读 priority/weight/port/target 四元组与 TTL):
$ dig SRV _avatars._tcp.libravatar.org +noall +answer
_avatars._tcp.libravatar.org. 1028 IN SRV 0 0 80 cdn.libravatar.org.
四元组含义:priority=0(最高优先级组)weight=0(组内不分权重)port=80 target=cdn.libravatar.org。查自己的域名(无记录则输出为空,客户端将回落 libravatar.org):
$ dig +short SRV _avatars._tcp.example.com
(空输出 = 无记录)
自托管域名要配的就是这两条(HTTP 用 _avatars._tcp、HTTPS 用 _avatars-sec._tcp,TTL 至少 1 天),配完可用官方自检工具 https://www.libravatar.org/tools/check 复核。
七、排障实录
7.1 manage.py 在错误目录执行
在非 /opt/ivatar 目录跑 python manage.py … 只会得到 manage.py 不存在的报错。固定姿势:cd /opt/ivatar && .virtualenv/bin/python manage.py …,或做个别名 alias ivamanage='cd /opt/ivatar && .virtualenv/bin/python manage.py'。
7.2 dbshell 想 SELECT digest 却报列不存在
照片表(ivataraccount_photo)存的是二进制 data + format,digest 是 confirmedemail/openid 表上的生成列。真实报错与正解见 3.9——先用 information_schema 查列名,别凭想象写 SQL。
7.3 shell 里 import 模块路径写错
from ivataraccount.models import … 报 ModuleNotFoundError;正确路径是 ivatar.ivataraccount.models(见 3.10)。
7.4 验证结果"时对时错"
九成是缓存:同一哈希 7 天内命中旧响应(PHOTO_CACHE_TIMEOUT)。manage.py clear_cache 或换新哈希(见 5.2 坑注)。
7.5 改了配置 curl 行为没变
systemctl restart ivatar 忘了执行(见 4.1)。排查顺序:diffsettings 核对生效值 → 确认重启 → clear_cache → 换哈希复测。
八、常见问题
Q1:命令太多记不住? A:日常就 8 个——generate_secret_key(初始化)、migrate/showmigrations(升级)、collectstatic(升级)、createsuperuser(初始化)、clear_cache/diffsettings(排障)、dbshell(查数据)。其余按 3.1 清单随用随查。
Q2:升级 ivatar 的完整命令序列?
A:cd /opt/ivatar && git pull && .virtualenv/bin/pip install -r requirements.txt && .virtualenv/bin/python manage.py migrate && .virtualenv/bin/python manage.py collectstatic --noinput && systemctl restart ivatar,然后 curl /version/ 核对 commit_hash。
Q3:怎么确认线上跑的是哪个版本?
A:curl -s http://127.0.0.1:8080/version/ 看 commit_hash / version / application_version 三字段。
Q4:头像 URL 给业务方怎么拼?
A:https://你的域名/avatar/<md5(小写邮箱)>,参数 s(1–512)、d(默认头像)、f=y(强制默认);语义与全参数行为对照见 5.2 表。
Q5:怎么批量验证一批邮箱有没有头像?
A:循环 curl -s -o /dev/null -w "%{http_code}" 各哈希——302(默认头像/委派)与 200(有图)区分,404 需显式 d=404。
九、参考链接
- Django manage.py 与 django-extensions 命令文档(命令行为依据):https://docs.djangoproject.com/en/5.2/ref/django-admin/ 、https://django-extensions.readthedocs.io/
- Libravatar Wiki《Simple API》(哈希与 URL 参数语义):https://wiki.libravatar.org/api/
- Libravatar Wiki《Running your own instance》(SRV 记录规范):https://wiki.libravatar.org/running_your_own/
- 本专栏认知篇:《Libravatar 2.0 介绍与选型指南》
- 本专栏部署篇:《Ubuntu 22.04 安装 Libravatar 2.0 指南》
- 本专栏配置篇:《Libravatar 2.0 配置参数详解》