Libravatar 2.0 常用 CLI 命令与工具详解

运维 ivatar 2.0 的命令手册:manage.py 命令族、systemd/nginx 服务管理、curl 全参数头像 API 验证与 dig 验证 DNS SRV 联邦记录,全部命令与回显来自真实部署实例。

LibravatarLibravatar头像服务2026/10/28

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 / Django3.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 -tivatar.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/e64c7d89f26bd1972efa854d13d7dd61200 image/png 177B有头像默认 80px
/avatar/e64c…dd61?s=1200 … 69B最小 1px
/avatar/e64c…dd61?s=512200 … 1880B上限 512
/avatar/e64c…dd61?s=600200 … 1880B超限被钳制到 512
/avatar/258d8dc9…abda4f(SHA256)200 image/png 177B与 MD5 结果一致
/avatar/cbcfb6c8…a0c2e5?d=identicon200 image/png 889B本地生成 identicon
/avatar/cbcfb6c8…a0c2e5?d=retro200 image/png 1267B本地生成复古像素
/avatar/cbcfb6c8…a0c2e5?d=mm302 -> /static/img/mm/80.png灰色人形剪影
/avatar/cbcfb6c8…a0c2e5?d=404404 text/html显式 404
/avatar/e64c…dd61?f=y302 -> /static/img/nobody/80.png强制默认(有头像也忽略)
/avatar/deadbeef302 -> /static/img/deadbeef.png非法哈希固定重定向
/avatar/c160f8cc…53d060 + Accept: image/webp200 image/webp 96BWebP 内容协商(用户照片路径)

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。

九、参考链接

  1. Django manage.py 与 django-extensions 命令文档(命令行为依据):https://docs.djangoproject.com/en/5.2/ref/django-admin/ 、https://django-extensions.readthedocs.io/
  2. Libravatar Wiki《Simple API》(哈希与 URL 参数语义):https://wiki.libravatar.org/api/
  3. Libravatar Wiki《Running your own instance》(SRV 记录规范):https://wiki.libravatar.org/running_your_own/
  4. 本专栏认知篇:《Libravatar 2.0 介绍与选型指南》
  5. 本专栏部署篇:《Ubuntu 22.04 安装 Libravatar 2.0 指南》
  6. 本专栏配置篇:《Libravatar 2.0 配置参数详解》