Ubuntu 22.04 安装 VoucherVault 指南

在 Ubuntu 22.04 服务器上从源码安装 VoucherVault v1.30.3 的完整实录:系统依赖、venv、.env、数据库迁移、Celery/uWSGI 启动、Nginx 反代、systemd 托管与排障。

VoucherVaultVoucherVaultDjango优惠券管理2026/10/25

Ubuntu 22.04 安装 VoucherVault 指南

目标:在一台 Ubuntu 22.04 服务器上,从源码完整安装 VoucherVault(Django + uWSGI + Celery + Redis 架构),完成数据库初始化、管理员创建、服务启动、Nginx 反向代理配置与功能验证。全部命令与回显均来自 2026-10-25 的真实安装实录。VoucherVault 是什么、是否适合你的场景,请先读《VoucherVault 介绍与选型指南》。

一、环境信息

项目值
操作系统Ubuntu 22.04.5 LTS (Jammy Jellyfish),x86_64
软件版本VoucherVault v1.30.3(tag,提交 00fb937,2026-10-09 发布)
Python3.10.12(系统自带)
关键依赖Django 5.2.18 / uWSGI 2.0.31 / Celery 5.6.3 / Apprise 1.9.9 / psycopg2-binary
Redis6.0.16(Ubuntu 仓库版)
Nginx1.18.0(Ubuntu 仓库版)
安装路径/opt/vouchervault/(源码 app/、虚拟环境 venv/、日志 logs/)
运行用户www-data(UID 33,与官方运行时保持一致)
数据库SQLite3(默认,数据文件 /opt/vouchervault/app/database/db.sqlite3)

💡 说明:VoucherVault 官方只维护一体化交付物,裸机安装需要自行拼装 Python 依赖与进程托管,本篇把这条路径完整走通。需要 PostgreSQL 作为后端库时,差异点见文末「进阶」一节。

二、安装系统依赖

VoucherVault 是 Python/Django 应用,运行前需要 Python 3、构建工具(编译 uWSGI、psycopg2 等)、Ghostscript(PDF 处理)、Redis(Celery 消息队列)与 Nginx(反向代理)。以 root 身份执行:

apt-get update
apt-get install -y python3 python3-pip python3-venv python3-dev git curl \
    redis-server nginx-light ghostscript gcc libc6-dev libpq-dev \
    netcat-openbsd postgresql-client unzip media-types

验证关键组件版本:

python3 --version
Python 3.10.12
redis-server --version
Redis server v=6.0.16 sha=00000000:0 malloc=jemalloc-5.2.1 bits=64 build=d6ed09173b889769
nginx -v
nginx version: nginx/1.18.0 (Ubuntu)

三、获取源码

把官方仓库克隆到 /opt/vouchervault/app 并切换到最新稳定版 v1.30.3:

mkdir -p /opt/vouchervault
git clone --depth 1 --branch v1.30.3 https://github.com/l4rm4nd/VoucherVault.git /opt/vouchervault/app
Cloning into '/opt/vouchervault/app'...
...
Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

确认版本:

cd /opt/vouchervault/app && git describe --tags
v1.30.3

四、创建虚拟环境并安装依赖

官方交付物在构建期用 pip 安装 requirements.txt,裸机用 venv 隔离:

python3 -m venv /opt/vouchervault/venv
source /opt/vouchervault/venv/bin/activate
pip install --upgrade pip
pip install -r /opt/vouchervault/app/requirements.txt

依赖较多(Django、uWSGI、Celery、Apprise、psycopg2-binary 等),编译 uWSGI 需要一两分钟。装完抽查关键包:

pip list | grep -iE "^(django|uwsgi|celery|redis|apprise|django-celery-beat|mozilla-django-oidc|django-pwa|psycopg2)"
apprise                     1.9.9
celery                      5.6.3
Django                      5.2.18
django-celery-beat          2.9.0
django-pwa                  2.0.1
mozilla-django-oidc         5.0.2
psycopg2-binary             2.9.11
redis                       6.4.0
uWSGI                       2.0.31

五、初始化配置(.env)

VoucherVault 通过项目根目录的 .env 文件读取配置(settings.py 用 python-dotenv 自动加载)。创建 /opt/vouchervault/app/.env:

cat > /opt/vouchervault/app/.env <<'EOF'
DOMAIN=localhost
SECURE_COOKIES=False
SESSION_COOKIE_AGE=30
SESSION_EXPIRE_AT_BROWSER_CLOSE=False
EXPIRY_THRESHOLD_DAYS=30
TZ=Asia/Shanghai
REDIS_URL=redis://127.0.0.1:6379/0
EOF

关键项说明:

  • DOMAIN:对外访问域名或 IP,决定 ALLOWED_HOSTS 与 CSRF_TRUSTED_ORIGINS,必须与实际访问地址一致,否则登录提交会被 CSRF 拦截;
  • REDIS_URL:Redis 连接串,Celery 任务队列依赖它;
  • EXPIRY_THRESHOLD_DAYS:到期前多少天首次提醒;
  • SECRET_KEY 不显式设置时每次启动随机生成(会导致既有会话失效),生产环境建议生成一个固定值写入 .env。

全部环境变量的逐项说明见本专栏《VoucherVault 配置参数详解》。

六、修正目录属主

应用以低权限 www-data(UID 33)运行,数据库文件、静态文件、日志目录都要可写:

mkdir -p /opt/vouchervault/app/database /opt/vouchervault/logs
chown -R www-data:www-data /opt/vouchervault

七、初始化数据库与静态文件

以下命令均以 www-data 身份在 /opt/vouchervault/app 下执行(用 runuser -u www-data -- 切换):

cd /opt/vouchervault/app
runuser -u www-data -- /opt/vouchervault/venv/bin/python manage.py makemigrations
runuser -u www-data -- /opt/vouchervault/venv/bin/python manage.py makemigrations myapp
No changes detected
No changes detected in app 'myapp'
runuser -u www-data -- /opt/vouchervault/venv/bin/python manage.py migrate

首次执行会应用全部迁移,尾部输出如下:

  Applying myapp.0027_add_view_mode_and_is_pinned... OK
  Applying myapp.0028_alter_userpreference_show_description_and_more... OK
  Applying myapp.0029_add_currency_to_item_and_userpreference... OK
  Applying myapp.0030_transaction_date_editable... OK
  Applying sessions.0001_initial... OK

收集静态文件(STATIC_ROOT 为 myapp/static/,稍后由 uWSGI/Nginx 提供):

runuser -u www-data -- /opt/vouchervault/venv/bin/python manage.py collectstatic --no-input --verbosity=0
ls /opt/vouchervault/app/myapp/static
admin
assets
css
fonts
images

八、创建定时任务与管理员

VoucherVault 的到期提醒依赖两条 Celery 周期任务,官方启动脚本在首次启动时通过自定义命令 create_default_periodic_tasks 创建:

runuser -u www-data -- /opt/vouchervault/venv/bin/python manage.py create_default_periodic_tasks
Default periodic tasks created successfully.

创建管理员账号(示例账号 admin,密码 q1w2e3r4;官方流程是随机生成密码打印到日志,裸机手动设置更可控):

runuser -u www-data -- /opt/vouchervault/venv/bin/python manage.py shell -c "from django.contrib.auth.models import User; User.objects.create_superuser('admin','admin@example.com','q1w2e3r4')"
18 objects imported automatically (use -v 2 for details).

九、启动服务

VoucherVault 裸机运行需要 4 个进程:Redis、Celery Worker、Celery Beat、uWSGI。

9.1 启动 Redis

redis-server --daemonize yes --port 6379
redis-cli ping
PONG

9.2 启动 Celery Worker 与 Beat

cd /opt/vouchervault/app
runuser -u www-data -- /opt/vouchervault/venv/bin/celery -A myproject worker -l info --detach \
    --logfile=/opt/vouchervault/logs/celery-worker.log \
    --pidfile=/opt/vouchervault/logs/celery-worker.pid
runuser -u www-data -- /opt/vouchervault/venv/bin/celery -A myproject beat -l info --detach \
    --scheduler django_celery_beat.schedulers:DatabaseScheduler \
    --logfile=/opt/vouchervault/logs/celery-beat.log \
    --pidfile=/opt/vouchervault/logs/celery-beat.pid

检查日志确认连上 Redis:

tail -5 /opt/vouchervault/logs/celery-worker.log
[2026-10-25 01:40:18,299: INFO/MainProcess] Connected to redis://127.0.0.1:6379/0
[2026-10-25 01:40:18,300: INFO/MainProcess] mingle: searching for neighbors
[2026-10-25 01:40:19,306: INFO/MainProcess] mingle: all alone
[2026-10-25 01:40:19,313: INFO/MainProcess] celery@vouchervault-server ready.

⚠️ Beat 必须带 --scheduler django_celery_beat.schedulers:DatabaseScheduler,否则不会读取数据库里刚创建的周期任务,到期提醒不会触发。

9.3 启动 uWSGI

官方交付物内的 docker_uwsgi.ini 写的是构建期路径 /opt/app,裸机按实际路径改写为 /opt/vouchervault/uwsgi.ini:

cat > /opt/vouchervault/uwsgi.ini <<'EOF'
[uwsgi]
http-socket = :8000
chdir = /opt/vouchervault/app
module = myproject.wsgi
master = 1
processes = 2
threads = 2
uid = www-data
gid = www-data
static-map = /static=/opt/vouchervault/app/myapp/static/
disable-logging = true
buffer-size=32768
EOF
chown www-data:www-data /opt/vouchervault/uwsgi.ini

启动(守护进程方式,日志写入 logs 目录):

runuser -u www-data -- /opt/vouchervault/venv/bin/uwsgi --ini /opt/vouchervault/uwsgi.ini \
    --die-on-term --daemonize /opt/vouchervault/logs/uwsgi.log
tail -8 /opt/vouchervault/logs/uwsgi.log
[uwsgi-static] added mapping for /static => /opt/vouchervault/app/myapp/static/
*** Operational MODE: preforking+threaded ***
WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0x5e922d50f7a0 pid: 6541 (default app)
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 6541)
spawned uWSGI worker 1 (pid: 6543, cores: 2)
spawned uWSGI worker 2 (pid: 6544, cores: 2)

十、功能验证

10.1 HTTP 冒烟测试

curl -s -o /dev/null -w 'GET / -> %{http_code}\n' http://127.0.0.1:8000/
GET / -> 302

根路径按浏览器语言重定向到 /en/ 等语言前缀路径。登录页(Django 自带认证挂在 accounts/ 下):

curl -s -o /dev/null -w 'GET /en/accounts/login/ -> %{http_code}\n' http://127.0.0.1:8000/en/accounts/login/
GET /en/accounts/login/ -> 200
curl -s http://127.0.0.1:8000/en/accounts/login/ | grep -o "<title>[^<]*"
<title>VoucherVault

10.2 会话登录与 REST API 冒烟

模拟表单登录(先取 CSRF Cookie 与令牌,再提交账号密码):

rm -f /tmp/cj
TOKEN=$(curl -s -c /tmp/cj http://127.0.0.1:8000/en/accounts/login/ | grep -o 'csrfmiddlewaretoken" value="[^"]*' | head -1 | cut -d'"' -f3)
curl -s -b /tmp/cj -c /tmp/cj -o /dev/null -w 'POST login -> %{http_code} redirect=%{redirect_url}\n' \
    --data-urlencode "csrfmiddlewaretoken=$TOKEN" \
    --data-urlencode "username=admin" \
    --data-urlencode "password=q1w2e3r4" \
    http://127.0.0.1:8000/en/accounts/login/
POST login -> 302 redirect=http://127.0.0.1:8000/

302 回首页即登录成功。REST API 走 Bearer Token 而非会话认证(详见本专栏《VoucherVault API 使用详解》),未带 Token 时返回:

curl -s -w '\nHTTP %{http_code}\n' http://127.0.0.1:8000/en/api/get/stats
Unauthorized. Invalid or missing authorization token.
HTTP 403

这同时证明 API 鉴权拦截在生效。

十一、Nginx 反向代理(生产推荐)

uWSGI 以 http-socket 监听 8000,前面挂 Nginx 做 TLS 终止与静态文件直发。创建 /etc/nginx/sites-available/vouchervault:

server {
    listen 80;
    server_name vouchervault.example.com;   # 改成你的域名,并同步修改 .env 的 DOMAIN

    client_max_body_size 20M;

    location /static/ {
        alias /opt/vouchervault/app/myapp/static/;
        expires 7d;
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

启用并重载:

ln -sf /etc/nginx/sites-available/vouchervault /etc/nginx/sites-enabled/vouchervault
rm -f /etc/nginx/sites-enabled/default
nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
systemctl reload nginx

通过 Nginx(80 端口)验证代理与静态直发:

curl -s -o /dev/null -w 'GET / -> %{http_code} redirect=%{redirect_url}\n' http://127.0.0.1/
GET / -> 302 redirect=http://127.0.0.1/en/
curl -s -o /dev/null -w 'GET /static/css/django-pwa-app.css -> %{http_code}\n' http://127.0.0.1/static/css/django-pwa-app.css
GET /static/css/django-pwa-app.css -> 200

⚠️ 上线 HTTPS 后,把 .env 中 SECURE_COOKIES 改为 True 并重启 uWSGI,开启安全 Cookie 与 HSTS。

十二、systemd 托管(生产建议)

手动 --daemonize 适合安装验证,生产建议交给 systemd。以下三个单元文件按上文路径编写,可直接落盘。

/etc/systemd/system/vouchervault-web.service:

[Unit]
Description=VoucherVault uWSGI application server
After=network.target redis-server.service

[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/opt/vouchervault/app
ExecStart=/opt/vouchervault/venv/bin/uwsgi --ini /opt/vouchervault/uwsgi.ini --die-on-term
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

/etc/systemd/system/vouchervault-celery.service:

[Unit]
Description=VoucherVault Celery worker
After=network.target redis-server.service

[Service]
Type=forking
User=www-data
Group=www-data
WorkingDirectory=/opt/vouchervault/app
ExecStart=/opt/vouchervault/venv/bin/celery -A myproject worker -l info --detach --logfile=/opt/vouchervault/logs/celery-worker.log --pidfile=/opt/vouchervault/logs/celery-worker.pid
PIDFile=/opt/vouchervault/logs/celery-worker.pid
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

/etc/systemd/system/vouchervault-celery-beat.service:

[Unit]
Description=VoucherVault Celery beat scheduler
After=network.target redis-server.service

[Service]
Type=forking
User=www-data
Group=www-data
WorkingDirectory=/opt/vouchervault/app
ExecStart=/opt/vouchervault/venv/bin/celery -A myproject beat -l info --detach --scheduler django_celery_beat.schedulers:DatabaseScheduler --logfile=/opt/vouchervault/logs/celery-beat.log --pidfile=/opt/vouchervault/logs/celery-beat.pid
PIDFile=/opt/vouchervault/logs/celery-beat.pid
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

启用(每行一个单元):

systemctl daemon-reload
systemctl enable vouchervault-web.service
systemctl enable vouchervault-celery.service
systemctl enable vouchervault-celery-beat.service
systemctl start vouchervault-web.service
systemctl start vouchervault-celery.service
systemctl start vouchervault-celery-beat.service

注:uWSGI 单元使用 Type=notify,需要 uWSGI 的 systemd 通知支持;若启动报 notify 错误,把 Type=notify 改为 Type=simple 并去掉 --daemonize(上文单元文件默认未带该参数,直接以前台方式运行)。

十三、进阶:切换 PostgreSQL

默认 SQLite 适合个人/家庭;多人共用建议 PostgreSQL。差异点:

  1. 安装并初始化 PostgreSQL 实例,建库建账号(依赖包 libpq-dev、postgresql-client 与 Python 侧 psycopg2-binary 已在上文装好);
  2. .env 追加:DB_ENGINE=postgres、POSTGRES_USER、POSTGRES_PASSWORD、POSTGRES_DB、POSTGRES_HOST、POSTGRES_PORT;
  3. 重新执行第七节迁移命令,再启动服务。

十四、排障实录

1. curl 模拟登录返回 403「CSRF verification failed」

完整报错页含 Reason given for failure: CSRF token from POST has incorrect length.。原因是 POST 体里的 csrfmiddlewaretoken 与 Cookie 不匹配(提取失败或没带 Cookie)。解法:务必 curl -c 存 Cookie、-b 带 Cookie,用 --data-urlencode 原样提交从登录页提取的 64 位令牌。若换用域名访问,还要确认 .env 的 DOMAIN 与访问地址一致——它决定 CSRF_TRUSTED_ORIGINS。

2. 调用 /api/get/stats 返回 403「Unauthorized. Invalid or missing authorization token.」

该端点不做会话认证,要求 Authorization: Bearer <api_token> 头;Token 存于后台「App settings」(模型 AppSettings.api_token,UUID 格式,可在管理后台重新生成)。详见本专栏《VoucherVault API 使用详解》。

3. Celery Beat 启动了但到期提醒从不触发

检查启动参数是否漏了 --scheduler django_celery_beat.schedulers:DatabaseScheduler。默认 scheduler 读本地文件而非数据库,create_default_periodic_tasks 创建的任务不会被调度。

4. 静态文件 404

STATIC_ROOT 是 myapp/static/(不是常见的 static/),修改源码或升级版本后必须重跑 collectstatic;uWSGI 的 static-map 与 Nginx 的 alias 都要指向这个目录。

5. uWSGI 日志出现 !!! no internal routing support, rebuild with pcre support !!!

Ubuntu 仓库版 uWSGI 或未启用 PCRE 的编译产物的提示,对本案无影响(未用到内部路由),可忽略。

十五、参考链接