OpenWISP 1.3.2 常用 CLI 与运维命令详解
OpenWISP 1.3.2 常用 CLI 与运维命令:migrate/createsuperuser/collectstatic、OpenWISP 专有管理命令、Celery 巡检、systemd 日志、备份恢复与七类故障排查,回显均为 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/celery | worker/beat 状态、任务列表、队列清理 | 九 |
| systemd | systemctl / 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 完整恢复顺序
- 停三个 systemd 单元;
pg_restore覆盖主库(先dropdb/createdb,再恢复);- 恢复
media/、settings.py; collectstatic --noinput;- 启动
uwsgi-openwisp→celery-openwisp→celery-beat-openwisp; - 验证:登录后台、
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
排查顺序(从下往上):
- Socket 是否存在、属主是否正确:
ls -l /run/uwsgi/openwisp.sock; - uWSGI 是否 active:
systemctl is-active uwsgi-openwisp; uwsgi.log末 20 行有无permission、already bound、unable to load app;- Nginx 是否指向正确 socket:
grep uwsgi_pass /etc/nginx/sites-enabled/openwisp.conf; - 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 路径、用户、权限),不在代码。
十三、参考链接
- OpenWISP 官方文档(
stable,26.09):https://openwisp.io/docs/stable/ - 官方安装文档:https://openwisp.io/docs/stable/controller/developer/installation.html
- 配置项参考:https://openwisp.io/docs/stable/controller/user/settings.html
- 源码仓库:https://github.com/openwisp/openwisp-controller
- PyPI 版本页:https://pypi.org/project/openwisp-controller/
- Django 管理命令参考:https://docs.djangoproject.com/en/5.2/ref/django-admin/
- Celery 监控与巡检命令:https://docs.celeryq.dev/en/stable/userguide/monitoring.html
- PostgreSQL
pg_dump/pg_restore:https://www.postgresql.org/docs/14/app-pgdump.html
版本说明:文中版本号与命令回显采集于 2026-10-28,
openwisp-controller最新稳定版为 1.3.2(PyPI)。安装步骤见 《OpenWISP 1.3.2 Ubuntu 22.04 安装指南》,参数含义见 《OpenWISP 1.3.2 配置参数详解》,选型对比见 《OpenWISP 介绍与选型指南》。