OpenWISP 1.3.2 常用 CLI 与运维命令详解

OpenWISP 1.3.2 常用 CLI 与运维命令:migrate/createsuperuser/collectstatic、OpenWISP 专有管理命令、Celery 巡检、systemd 日志、备份恢复与七类故障排查,回显均为 2026-10-28 实测。

OpenWISPOpenWISP网络管理CLI 命令运维2026/10/28

OpenWISP 1.3.2 常用 CLI 与运维命令详解

目标:把 OpenWISP Controller(1.3.2)日常运维真正要用到的命令行收拢成一本手册——manage.py 迁移与用户管理、OpenWISP 专有管理命令、Celery 任务队列、systemd 与日志、备份恢复,以及七类真实故障的排查路径。所有命令均在 /opt/openwisp/app 实例上执行,回显为原样照抄;安装过程见 《OpenWISP 1.3.2 Ubuntu 22.04 安装指南》,参数含义见 《OpenWISP 1.3.2 配置参数详解》。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS
  • 应用版本:openwisp-controller 1.3.2、Django 5.2.18、Python 3.10.12
  • 数据库:PostgreSQL 14.24 + PostGIS 3.2.0,库名 openwisp,用户 openwisp
  • 缓存与队列:Redis 6.0.16(127.0.0.1:6379)
  • 应用服务器:uWSGI 2.0.20(Unix Socket /run/uwsgi/openwisp.sock)+ Nginx 1.18.0
  • 项目路径:/opt/openwisp/app;虚拟环境:/opt/openwisp/venv
  • 配置目录:/etc/openwisp/;日志目录:/var/log/openwisp/
  • 运行用户:www-data;站点域名:openwisp.example.com
  • 主机名:openwisp-lab
  • 采集日期:2026-10-28

💡 说明:本文所有 manage.py 命令都以 www-data 身份、在 /opt/openwisp/app 目录下执行。用 root 直接跑会产生一批属主为 root 的产物(static/、导出 CSV),后续 Web 进程就写不动——第十一章有一个真实案例。

二、命令工具概览

OpenWISP 没有独立的运维 CLI,日常操作分成五组工具,职责边界清晰:

工具可执行文件职责本文对应章节
Django 管理入口/opt/openwisp/venv/bin/python manage.py迁移、用户、静态文件、缓存、自定义命令四~八
Celery CLI/opt/openwisp/venv/bin/celeryworker/beat 状态、任务列表、队列清理九
systemdsystemctl / journalctl服务生命周期与日志十
数据库客户端psql / pg_dump / pg_restore备份、恢复、临时查询十一
Redis 客户端redis-cli五个库的键空间巡检七、十一

虚拟环境 bin/ 下还装有四个 openwisp-* 脚本,实测清单:

ls /opt/openwisp/venv/bin | grep -iE "openwisp|uwsgi|celery|daphne"

实测回显:

celery
daphne
openwisp-commit
openwisp-pre-push-hook
openwisp-qa-check
openwisp-qa-format

注意这四项不是运行期运维命令,而是 OpenWISP 团队用于自身代码质量检查的开发工具(openwisp-qa-check 会检查迁移文件、flake8、提交信息格式等)。生产环境不需要调用它们,也不必因它们报错而担心——实测在非 git 目录下执行 openwisp-qa-check --help 会先打印:

fatal: not a git repository (or any of the parent directories): .git

随后才输出用法。这是脚本先探测 git 环境导致的正常现象。

真正与 OpenWISP 业务相关的命令,全部以 Django 子命令形式注册在 manage.py 里,分组前缀即所属应用([config]、[openwisp_ipam]、[openwisp_notifications]、[openwisp_users])。

三、前置条件

运维命令大多要连数据库和 Redis,执行前确认三件事:

systemctl is-active uwsgi-openwisp celery-openwisp celery-beat-openwisp
redis-cli ping
PGPASSWORD=q1w2e3r4 psql -h 127.0.0.1 -U openwisp -d openwisp -tAc "select current_database()"

实测回显:

active
active
active
PONG
openwisp

统一的环境准备方式(后续章节默认已设置):

cd /opt/openwisp/app
export PATH=/opt/openwisp/venv/bin:$PATH
export DJANGO_SETTINGS_MODULE=openwisp2.settings
su -s /bin/bash www-data -c 'cd /opt/openwisp/app && /opt/openwisp/venv/bin/python manage.py check'

实测回显:

System check identified no issues (0 silenced).

⚠️ 注意:实验服务器没有 sudo,本文用 su -s /bin/bash www-data -c '...' 切换身份;你的生产机若有 sudo,等价写法是 sudo -u www-data -E /opt/openwisp/venv/bin/python manage.py <子命令>。

四、manage.py 全局用法

先看清命令全集,再决定用哪个:

/opt/openwisp/venv/bin/python manage.py --help

输出按应用分组,节选(只保留有自定义命令的分组):

[auth]
[authtoken]
[channels]
[config]
    clear_last_ip
    print_cache_dependencies
[contenttypes]
[daphne]
[django]
[django_extensions]
    export_emails
[drf_yasg]
[gis]
[import_export]
[openwisp_ipam]
    export_subnet
    import_subnet
[openwisp_notifications]
    create_notification
    populate_notification_preferences
[openwisp_users]
    export_users
[rest_framework]
[reversion]
[sessions]
[staticfiles]

四个通用子命令的用途:

子命令用途风险
check静态检查配置与模型,不连业务数据无
diffsettings列出与 Django 默认值不同的配置项无
shell -c "..."执行一段 Python,直接操作 ORM 与缓存中(写操作会落库)
dbshell打开 psql 会话中(手改数据风险高)

check --deploy 用于上线前安全体检,实测在补齐 HTTPS 相关项后只剩地理编码告警(settings_hardened 设置模块):

DJANGO_SETTINGS_MODULE=openwisp2.settings_hardened /opt/openwisp/venv/bin/python manage.py check --deploy

实测末尾回显(前面先打印一段 geopy 异常栈,因为探测地点时连不上外网):

System check identified some issues:

WARNINGS:
?: Geocoding service is experiencing issues or is not properly configured

System check identified 1 issue (0 silenced).

该告警的成因与处理见第十二章 12.7。

五、数据库迁移与状态

5.1 执行迁移

首次安装与每次升级后都必须执行:

su -s /bin/bash www-data -c 'cd /opt/openwisp/app && /opt/openwisp/venv/bin/python manage.py migrate'

已迁移完成的实例上再次执行,实测回显:

Operations to perform:
  Apply all migrations: account, admin, auth, authtoken, config, connection, contenttypes, geo, openwisp_ipam, openwisp_notifications, openwisp_users, pki, reversion, sessions, sites, socialaccount, subnet_division
Running migrations:
  No migrations to apply.

首次执行时,这份清单会逐条 Applying config.0001_squashed_... 刷屏。实测该实例共有 186 条已应用迁移、0 条未应用:

/opt/openwisp/venv/bin/python manage.py showmigrations | grep -c "\[X\]"
/opt/openwisp/venv/bin/python manage.py showmigrations | grep -c "\[ \]"
186
0

5.2 查看某个应用的迁移明细

/opt/openwisp/venv/bin/python manage.py showmigrations config | head -12
config
 [X] 0001_squashed_0002_config_settings_uuid
 [X] 0003_template_tags
 [X] 0004_add_device_model
 [X] 0005_populate_device
 [X] 0006_config_device_not_null
 [X] 0007_simplify_config
 [X] 0008_update_indexes
 [X] 0009_device_system
 [X] 0010_auto_20180106_1814
 [X] 0011_update_device_mac_address
 [X] 0012_auto_20180219_1501

[X] 表示已应用,[ ] 表示未应用。只回滚/前进单个应用用 migrate config 0012 这类“迁移名”参数。

5.3 升级前检查是否有未生成的迁移

执行 makemigrations 的对象通常是自己的项目,但升级依赖包后检查一次可以提前发现模型漂移:

/opt/openwisp/venv/bin/python manage.py makemigrations --check --dry-run
No changes detected

--check 在检测到变更时返回非零退出码,适合放进部署脚本做前置校验。

📌 要点:migrate 是 原子性有限的。PostgreSQL 支持事务 DDL,但个别迁移会显式关闭事务。升级前先按第十一章做一次 pg_dump,出问题才能回到迁移前状态。

六、用户、组织与权限命令

6.1 创建超级用户

交互式:

su -s /bin/bash www-data -c 'cd /opt/openwisp/app && /opt/openwisp/venv/bin/python manage.py createsuperuser'

实测按提示依次输入 admin / admin@example.com / 密码 `/ 再输一次后回显:

Superuser created successfully.

非交互式(适合脚本化初始化):

DJANGO_SUPERUSER_PASSWORD=q1w2e3r4 /opt/openwisp/venv/bin/python manage.py createsuperuser --noinput --username admin --email admin@example.com

6.2 核对用户是否落库

/opt/openwisp/venv/bin/python manage.py shell -c "from openwisp_users.models import User; print(User.objects.values_list('username','email'))"

实测回显:

<QuerySet [('admin', 'admin@example.com')]>

注意查询用的是 OpenWISP 自己的 User 模型(AUTH_USER_MODEL = 'openwisp_users.User'),不是 django.contrib.auth.models.User。

6.3 导出用户清单

/opt/openwisp/venv/bin/python manage.py export_users

成功回显:

User data exported successfully to openwisp_exported_users.csv!

导出文件落在当前工作目录(即 /opt/openwisp/app/openwisp_exported_users.csv,实测 399 字节),首两行:

id,username,email,password,first_name,last_name,is_staff,is_active,date_joined,expiration_date,phone_number,birth_date,location,notes,language,"organizations (organization_id, is_admin)"
a9fb7dc9-c8c8-4038-9a34-d80957238284,admin,admin@example.com,pbkdf2_sha256$1000000$9cd55Zp8qxTKHSANewY5Lo$FzvowE6CjOTwEOrEepb/KUqlsyqWB5t/5teyGd9QWl0=,,,True,True,2026-10-27 18:10:14.136787+00:00,,,,,,zh-hans,

⚠️ 注意:导出的 CSV 含密码哈希,属于敏感文件。迁移完成后应立即删除,不要留在 Web 根目录下(/opt/openwisp/app 不在 static/、media/ 暴露路径内,但仍应清理)。

6.4 修改密码

Django 自带 changepassword,走交互式输入:

/opt/openwisp/venv/bin/python manage.py changepassword admin

输入两次新密码后,预期回显为 Password changed successfully for user 'admin'(该命令会真实改写密码,为避免影响后续取证,本文未在实验实例上执行,故此处为预期结果)。该命令不会同步更新 export_users 生成的 CSV,两者无关联。

七、静态文件、缓存与会话

7.1 静态文件收集

每次升级 openwisp-utils、openwisp-notifications 等带前端资源的包后必须重跑:

su -s /bin/bash www-data -c 'cd /opt/openwisp/app && /opt/openwisp/venv/bin/python manage.py collectstatic --noinput'

首次回显:

413 static files copied to '/opt/openwisp/app/static'.

重复执行时(已是最新):

0 static files copied to '/opt/openwisp/app/static', 413 unmodified.

--noinput 避免因目标文件已存在而停在这里等确认。加 --clear 会先清空 STATIC_ROOT 再拷,适合怀疑残留旧文件时使用;代价是重启瞬间页面样式会短暂 404。

7.2 清空默认缓存

OpenWISP 的默认缓存库是 Redis 的 6 号库。用 Django 缓存 API 清理最安全(不会误伤队列与会话):

/opt/openwisp/venv/bin/python manage.py shell -c "from django.core.cache import caches; c=caches['default']; c.clear(); print('default cache cleared', c.get('probe'))"

实测回显:

default cache cleared None

c.get('probe') 返回 None 即为清理成功的旁证。

7.3 清空会话

会话单独放在 8 号库。清理后所有已登录用户会被登出,请安排在维护窗口:

/opt/openwisp/venv/bin/python manage.py shell -c "from django.core.cache import caches; caches['sessions'].clear(); print('sessions cache cleared')"

实测回显:

sessions cache cleared

7.4 键空间巡检

redis-cli info keyspace

清理前(会话库有键):

# Keyspace
db1:keys=3,expires=0,avg_ttl=0
db2:keys=2,expires=2,avg_ttl=85716517
db8:keys=2,expires=2,avg_ttl=86400000

清理 6、8 号库后再看,实测只剩:

# Keyspace
db1:keys=3,expires=0,avg_ttl=0
db2:keys=3,expires=3,avg_ttl=85716517

空库不会出现在 info keyspace 里,读不到 db6/db8 正是清空后的正常表现(db2 的键数从 2 变 3 是期间 celery worker 写入了新的任务结果,属正常波动)。库位分工:1 任务队列、2 任务结果、6 默认缓存、7 Channels、8 会话。合并库位会导致“清缓存顺手清掉会话”这类事故,详见 《OpenWISP 1.3.2 配置参数详解》。

⚠️ 禁止用 redis-cli flushall 做“大扫除”。它会同时清掉队列里的待执行任务与会话,表现为“用户在跑的任务突然消失 + 全员掉线”。

八、OpenWISP 专有管理命令

8.1 clear_last_ip

用途:清除“活跃设备”上缓存的最后 IP(当设备没有 WHOIS 记录时,这些 IP 可能是过期的)。

/opt/openwisp/venv/bin/python manage.py clear_last_ip --noinput

实测回显(该实例未配置 WHOIS 服务):

CommandError: WHOIS lookup is not configured. Set OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT and OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY to enable this command.

说明该命令依赖 WHOIS 配置,不是无条件可用。启用方式:在 settings.py 中设置 OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT、OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY(GeoIP 服务的账号与密钥),再重跑。--noinput 用于跳过交互确认,便于 cron 调用。查看完整参数:

usage: manage.py clear_last_ip [-h] [--noinput] [--version] [-v {0,1,2,3}]
                               [--settings SETTINGS] [--pythonpath PYTHONPATH]
                               [--traceback] [--no-color] [--force-color]
                               [--skip-checks]

8.2 print_cache_dependencies

用途:打印缓存失效规则表——哪个模型的哪个事件,会触发哪个视图缓存被清。排查“改了配置但页面还显示旧值”时,先用它确认规则是否注册。

/opt/openwisp/venv/bin/python manage.py print_cache_dependencies

实测输出节选(该实例共注册 19 条失效规则):

any (config_deactivated)
  target: DeviceChecksumView.invalidate_get_device_cache_on_config_deactivated
    resolve: instance   on_create: False   on_commit: True
    uid: cache_invalidation.app.any.config_deactivated.invalidate_get_device_cache_on_config_deactivated.instance

config.device (post_save)
  target: DeviceChecksumView.invalidate_get_device_cache
    resolve: instance   on_create: True   on_commit: True
    uid: cache_invalidation.app.config.device.post_save.invalidate_get_device_cache.instance.oncreate

config.devicegroup (post_save)
  target: invalidate_devicegroup_cache_change_handler
    resolve: instance   on_create: False   on_commit: True
    uid: cache_invalidation.app.config.devicegroup.post_save.invalidate_devicegroup_cache_change_handler.instance

读法:on_commit: True 表示失效动作推迟到数据库事务提交后执行(避免回滚时已清缓存);on_create 区分新增与更新。若这里缺了某个模型的规则,说明对应处理器没注册,页面缓存不会自动失效。

8.3 export_users

已在 6.3 节说明。补充两个易踩的坑:导出文件名固定、写在当前工作目录;若该文件属主不是 www-data,会直接抛 PermissionError(第十二章 12.4 有完整复现)。

8.4 create_notification

用途:向指定用户/组织批量投递站内通知。先看参数:

/opt/openwisp/venv/bin/python manage.py create_notification --help
usage: manage.py create_notification [-h] [--version] [-v {0,1,2,3}]
                                     [--settings SETTINGS] [--pythonpath PYTHONPATH]
                                     [--traceback] [--no-color] [--force-color]
                                     [--skip-checks]

帮助里只列通用参数,业务参数由命令内部提示(该命令设计为交互式向导,逐项询问接收人类型、对象、级别、消息文本)。批量维护场景建议改用 Python 调用 ORM:

/opt/openwisp/venv/bin/python manage.py shell -c "from openwisp_notifications.models import Notification; print(Notification.objects.count())"

8.5 IPAM 子网导入导出

[openwisp_ipam] 下有两个命令:export_subnet、import_subnet。它们操作的是已存在的子网对象,空库执行会提示找不到对象;正确顺序是先在后台“IP 地址管理”里建好子网,再导出成 CSV 做批量编辑,最后导入回来。

8.6 批量维护任务清单

manage.py --help 里出现 [django_extensions] export_emails,这是第三方包的邮件列表导出命令,与 OpenWISP 用户体系不耦合;需要按组织导出用户时应使用 export_users。

九、Celery 任务运维

9.1 查看 worker 是否在线

/opt/openwisp/venv/bin/celery -A openwisp2 inspect ping

实测回显:

->  celery@openwisp-lab: OK
        pong

1 node online.

1 node online. 是判断 worker 存活的唯一可靠依据——systemctl is-active 只能说明进程在,不能说明它连上了 broker。

9.2 列出 worker 已注册任务

/opt/openwisp/venv/bin/celery -A openwisp2 inspect registered

实测(45 行,其中 42 行是任务条目),节选:

->  celery@openwisp-lab: OK
    * openwisp_controller.config.tasks.auto_add_template_to_existing_configs
    * openwisp_controller.config.tasks.bulk_invalidate_config_get_cached_checksum
    * openwisp_controller.config.tasks.change_devices_templates
    * openwisp_controller.config.tasks.create_vpn_dh
    * openwisp_controller.config.tasks.invalidate_controller_views_cache
    * openwisp_controller.config.tasks.update_template_related_config_status
    * openwisp_controller.config.tasks_zerotier.trigger_zerotier_server_update_member
    * openwisp_controller.config.whois.tasks.cleanup_unreferenced_whois_records
    * openwisp_controller.connection.tasks.launch_command
    * openwisp_controller.connection.tasks.update_config
    * openwisp_controller.subnet_division.tasks.provision_extra_ips
    * openwisp_notifications.tasks.send_batched_email_notifications
    * openwisp_users.tasks.deactivate_expired_users
    * openwisp_users.tasks.password_expiration_email
    * whois_estimated_location_task

任务头像缺失(比如没有 openwisp_radius 相关任务)说明对应应用未装进 INSTALLED_APPS,这是最直接的“模块是否生效”检查法。

9.3 查看 worker 运行参数

/opt/openwisp/venv/bin/celery -A openwisp2 inspect stats

实测输出的 broker 段:

->  celery@openwisp-lab: OK
    {
        "broker": {
            "alternates": [],
            "connect_timeout": 4,
            "credential_provider": null,
            "failover_strategy": "round-robin",
            "heartbeat": 120.0,
            "hostname": "127.0.0.1",
            "insist": false,
            "login_method": null,
            "port": 6379,

生产排障时重点看两处:hostname/port 是否与 CELERY_BROKER_URL 一致;pool 段里的 max-concurrency 是否等于你设置的 --concurrency。

9.4 清空待执行队列

/opt/openwisp/venv/bin/celery -A openwisp2 purge -f

实测回显:

No messages purged from 1 queue.

-f 表示跳过确认。这会丢弃所有排队任务,只应在“任务积压且内容已过时(如批量配置下发参数写错)”时使用。清空前先看队列长度:

redis-cli -n 1 llen celery

返回值即积压条数。

9.5 重启顺序

两个 Celery 单元必须与 uWSGI 一起重启,且顺序影响短暂行为:

systemctl restart uwsgi-openwisp
systemctl restart celery-openwisp
systemctl restart celery-beat-openwisp

先重启 uWSGI 会让 Web 端立即使用新配置;如果先重启 Celery,worker 可能按旧配置连一个已不存在的队列地址并反复重连,日志里刷 consumer: Cannot connect to redis://...。

十、日志与 systemd 服务管理

10.1 三个单元的状态

systemctl is-active uwsgi-openwisp celery-openwisp celery-beat-openwisp
active
active
active

查看单进程内存(服务名与 PID 对应关系常被忽略):

systemctl show uwsgi-openwisp -p ActiveState -p MainPID -p MemoryCurrent

实测:

MainPID=7859
MemoryCurrent=428437504
ActiveState=active

MemoryCurrent 单位是字节,428437504 ≈ 408 MiB(4 进程 × 2 线程)。若该值长期贴近机器内存上限,应下调 processes 而不是加大机器。

10.2 systemd 日志

journalctl -u uwsgi-openwisp -n 5 --no-pager

实测(含一次失败重启的历史记录):

Oct 27 18:10:52 openwisp-lab systemd[1]: uwsgi-openwisp.service: Main process exited, code=exited, status=1/FAILURE
Oct 27 18:10:52 openwisp-lab systemd[1]: uwsgi-openwisp.service: Failed with result 'exit-code'.
Oct 27 18:10:56 openwisp-lab systemd[1]: Stopped uWSGI instance for OpenWISP Controller.
Oct 27 18:10:56 openwisp-lab systemd[1]: Started uWSGI instance for OpenWISP Controller.
Oct 27 18:10:56 openwisp-lab uwsgi[7859]: [uWSGI] getting INI configuration from /etc/openwisp/uwsgi.ini

以 www-data 身份执行 journalctl 会因权限不足拿不到日志,实测回显:

Hint: You are currently not seeing messages from other users and the system.
      Users in groups 'adm', 'systemd-journal' can see all messages.
      Pass -q to turn off this notice.
No journal files were opened due to insufficient permissions.

规范做法是把运维账号加入 systemd-journal 组,或用 sudo journalctl。

10.3 应用日志文件

ls -lh /var/log/openwisp/
total 60K
-rw-r--r-- 1 www-data www-data  52K Oct 27 18:22 openwisp.log
-rw-r----- 1 www-data www-data 5.5K Oct 27 18:14 uwsgi.log

openwisp.log 由 settings.py 的 LOGGING 配置写入,包含 Django 请求异常与任务异常;uwsgi.log 偏启动/协议层错误(如 chdir(): Permission denied 只会出现在这里)。排查 502 先看 uwsgi.log,排查业务 500 先看 openwisp.log。

10.4 Nginx 访问日志

tail -3 /var/log/nginx/openwisp.access.log
127.0.0.1 - - [27/Oct/2026:18:14:54 +0000] "GET /api/v1/ HTTP/1.1" 404 159 "-" "python-requests/2.34.2"
127.0.0.1 - - [27/Oct/2026:18:14:54 +0000] "GET /admin/config/vpn/ HTTP/1.1" 200 5244 "-" "python-requests/2.34.2"
127.0.0.1 - - [27/Oct/2026:18:21:24 +0000] "GET /media/probe.txt HTTP/1.1" 200 12 "-" "curl/7.81.0"

/api/v1/ 返回 404 是正常的:OpenWISP 的 API 根路径是 /api/v1/ 下的具体资源(如 /api/v1/controller/device/),根路径本身未注册视图。

10.5 Socket 与监听端口

ls -l /run/uwsgi/openwisp.sock
ss -ltn

实测:

srw-rw---- 1 www-data www-data 0 Oct 27 18:10 /run/uwsgi/openwisp.sock
State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess
LISTEN 0      511          0.0.0.0:80         0.0.0.0:*
LISTEN 0      244        127.0.0.1:5432       0.0.0.0:*
LISTEN 0      511        127.0.0.1:6379       0.0.0.0:*

uWSGI 不监听 TCP 端口,只提供 Unix Socket;对外 80 由 Nginx 提供。因此 ss -ltn 里看不到 8000/8080 之类端口是设计如此。Socket 权限 srw-rw---- www-data www-data 是 Nginx 能连通的前提,属主一变就 502。

十一、备份与恢复

11.1 数据库备份

mkdir -p /var/backups/openwisp
PGPASSWORD=q1w2e3r4 pg_dump -h 127.0.0.1 -U openwisp -Fc -f /var/backups/openwisp/openwisp-20261028.dump openwisp

-Fc 是自定义归档格式(压缩、可选择性恢复)。实测产物:

-rw-rw-r-- 1 postgres postgres 213K Oct 27 18:27 /var/backups/openwisp/openwisp-20261028.dump

核对归档条目数(pg_restore --list 每行以 ; 结尾):

pg_restore --list /var/backups/openwisp/openwisp-20261028.dump | grep -c ";"

实测:

511

11.2 恢复演练

恢复必须验证,否则等于没备份。实测流程与一次真实失败:

su - postgres -c "dropdb --if-exists openwisp_restore_test; createdb -O openwisp openwisp_restore_test"
su - postgres -c "psql -d openwisp_restore_test -c 'CREATE EXTENSION IF NOT EXISTS postgis;'"
su - postgres -c "pg_restore -d openwisp_restore_test --no-owner --no-privileges /var/backups/openwisp/openwisp-20261028.dump"
su - postgres -c "psql -d openwisp_restore_test -tAc 'select count(*) from openwisp_users_user'"

实测回显(CREATE EXTENSION 与用户计数):

CREATE EXTENSION
1

1 即 admin 用户被成功恢复。验证后删除临时库:

su - postgres -c "dropdb openwisp_restore_test"

⚠️ 注意:不要用 openwisp 账号执行 createdb。实测该账号无 CREATEDB 权限:

createdb: error: database creation failed: ERROR:  permission denied to create database

恢复演练应以 postgres 超级用户建库,再让 openwisp 作为 owner。同时 CreateExtension 由超级用户执行——普通账号恢复时会因无权建 PostGIS 扩展而大量报错。

11.3 需要一起备份的还有什么

对象路径说明
数据库pg_dump 产物设备、模板、用户、权限全在这里
媒体文件/opt/openwisp/app/media上传的固件、拓扑图、证书文件
配置/opt/openwisp/app/openwisp2/settings.py、/etc/openwisp/丢了要重建,含 SECRET_KEY
Redis可选队列与缓存可重建,丢失只影响在途任务与会话

Redis 的 AOF/RDB 不是必需备份项:清空后用户需重新登录,任务需重投。若业务要求零丢失在途任务,可在维护窗口先 redis-cli save 再做整机快照。

11.4 完整恢复顺序

  1. 停三个 systemd 单元;
  2. pg_restore 覆盖主库(先 dropdb/createdb,再恢复);
  3. 恢复 media/、settings.py;
  4. collectstatic --noinput;
  5. 启动 uwsgi-openwisp → celery-openwisp → celery-beat-openwisp;
  6. 验证:登录后台、celery inspect ping、打开任一设备配置页。

十二、故障排查手册

12.1 Error loading psycopg2 or psycopg module

现象:任何 manage.py 命令都起不来,报错指向数据库驱动。

原因:Django 的 PostGIS 后端需要可用的 psycopg 驱动,但系统只装了 libpq-dev,Python 层驱动缺失(或编译版缺少 pg_config 环境)。

处理:

/opt/openwisp/venv/bin/pip install "psycopg[binary]"
/opt/openwisp/venv/bin/pip show psycopg | head -2

实测安装的是 3.3.6,安装后 manage.py check 恢复正常。[binary] 后缀直接拉预编译轮子,避免现场编译。

12.2 uWSGI 启动即退出:chdir(): Permission denied

现象:systemctl status uwsgi-openwisp 显示 status=1/FAILURE,uwsgi.log 中:

chdir(): Permission denied [core/uwsgi.c line 2625]

原因:uwsgi.ini 里 chdir=/opt/openwisp/app,但 /opt/openwisp 目录权限为 700 root:root,www-data 无法进入父目录。

处理:

chmod 755 /opt/openwisp
chown -R www-data:www-data /opt/openwisp/app
systemctl restart uwsgi-openwisp
systemctl is-active uwsgi-openwisp

实测修复后返回 active。注意只改 /opt/openwisp 这一层的 x 权限即可,不要 chown -R 整个 /opt/openwisp(会把 venv 也改属主,虽无大碍但会拖慢后续 pip 操作)。

12.3 502 Bad Gateway

排查顺序(从下往上):

  1. Socket 是否存在、属主是否正确:ls -l /run/uwsgi/openwisp.sock;
  2. uWSGI 是否 active:systemctl is-active uwsgi-openwisp;
  3. uwsgi.log 末 20 行有无 permission、already bound、unable to load app;
  4. Nginx 是否指向正确 socket:grep uwsgi_pass /etc/nginx/sites-enabled/openwisp.conf;
  5. SELinux/AppArmor(Ubuntu 默认 AppArmor)是否拦截 Nginx 读 /run/uwsgi/。

九成 502 是第 1 步的属主问题——uWSGI 以 root 启动过、重启后 socket 属主变了,Nginx(www-data)就连不上。

12.4 export_users 抛 PermissionError

现象:

PermissionError: [Errno 13] Permission denied: 'openwisp_exported_users.csv'

原因:该 CSV 早前被 root 执行过一次而生成,文件属主是 root,之后以 www-data 身份重跑就写不进去。

处理(需要 root 权限改属主):

chown www-data:www-data /opt/openwisp/app/openwisp_exported_users.csv
su -s /bin/bash www-data -c 'cd /opt/openwisp/app && /opt/openwisp/venv/bin/python manage.py export_users'

修复后实测回显:

User data exported successfully to openwisp_exported_users.csv!

根治办法:不要用 root 跑 manage.py。同理适用于 collectstatic(会产生 root 属主的 static/,之后 Nginx 读不到新文件)。

12.5 clear_last_ip 报 WHOIS 未配置

CommandError: WHOIS lookup is not configured. Set OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT and OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY to enable this command.

这不是故障,是命令的前置条件未满足。两种处理:配置 GeoIP 账号密钥(并确保服务器能出网到 GeoIP 服务);或在无需 WHOIS 的场景下直接忽略该命令,设备 IP 仍会在设备上报时更新。

12.6 部分子命令带 --help 报 TypeError

实测 print_cache_dependencies --help、export_users --help 会抛:

TypeError: expected string or bytes-like object

原因:这两个命令继承了 openwisp_utils 的基类,在 argparse 构建阶段对一个 None 值做了正则匹配,属上游缺陷。不带 --help 直接执行完全正常(8.2、6.3 两节的真实输出即为证)。遇到时不要反复试 --help,直接跑或不跑。

12.7 check --deploy 报地理编码告警

?: (openwisp_controller.geo.E001) ...
	HINT: Geocoding services are not available. ...

原因:实验服务器无外网,geopy 调用 Nominatim 时抛 geopy.exc.GeocoderUnavailable ... Network is unreachable。生产机若能访问地理编码服务,该告警自动消失;纯内网部署可保留该告警,仅影响地址转经纬度功能,不影响设备与配置管理主流程。其余 4 条 HTTPS/安全告警按 《OpenWISP 1.3.2 配置参数详解》 第十一章的加固模板补齐后降到 1 条。

12.8 服务起不来的通用排查顺序

systemctl status uwsgi-openwisp --no-pager -l
journalctl -u uwsgi-openwisp -n 50 --no-pager
tail -50 /var/log/openwisp/uwsgi.log
su -s /bin/bash www-data -c 'cd /opt/openwisp/app && /opt/openwisp/venv/bin/python manage.py check'

四条命令覆盖了 95% 的场景:systemd 层 → 单元日志 → 应用日志 → Django 自检。check 能过而服务仍挂,问题一定在进程启动参数(socket 路径、用户、权限),不在代码。

十三、参考链接

版本说明:文中版本号与命令回显采集于 2026-10-28,openwisp-controller 最新稳定版为 1.3.2(PyPI)。安装步骤见 《OpenWISP 1.3.2 Ubuntu 22.04 安装指南》,参数含义见 《OpenWISP 1.3.2 配置参数详解》,选型对比见 《OpenWISP 介绍与选型指南》。