Apache STeVe 安装指南

在 Ubuntu 22.04 真实服务器上从零安装 Apache STeVe v3 投票系统,涵盖系统依赖、uv 双引导安装、虚拟环境同步、SQLite 建库、管理员账号写入、选举创建、投票与计票验证、systemd 托管及常见故障排障,全部命令与回显均实测

Apache STeVeApache STeVe2026/10/12

Apache STeVe 安装指南

目标:在真实 Ubuntu 22.04 服务器上从零安装 Apache STeVe v3(Python 实现的 STV 单一可转移投票系统),覆盖系统依赖、uv 环境搭建、SQLite 数据库初始化、管理员账号写入、选举创建、投票与计票验证及 systemd 托管。以下命令与回显均在 Ubuntu 22.04.5 真实环境逐步实测。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • Apache STeVe:v3(pyproject 版本 3.0.0,源码 trunk 提交 3222df7)
  • Python:3.12.15(uv 独立 Python,venv 内置 SQLite 3.53.1)
  • 包管理器:uv 0.13.0
  • Git:2.34.1;系统 SQLite:3.37.2
  • 源码目录:/opt/steve;数据库:/opt/steve/v3/server/steve.db
  • 服务端口:58383(HTTP 与 HTTPS 同端口,由 config.yaml 的证书项决定)
  • 关键依赖:asfquart 0.1.12、hypercorn 0.18.0、quart 0.22.0
  • 示例管理账号:admin(演示口令 q1w2e3r4,仅作团队演示记忆;STeVe v3 无本地口令登录,认证经 asfquart 对接 Apache OAuth,见 8.5)

二、前置条件

  • 一台 Ubuntu 22.04 服务器,root 权限。
  • 系统依赖包:python3 python3-pip python3-venv git sqlite3 net-tools libldap2-dev libsasl2-dev(后两项为依赖组 python-ldap 编译所需头文件)。
  • 可访问 GitHub(拉取源码)与 PyPI(拉取依赖包)。
  • STeVe v3 使用 SQLite 存储全部数据,无需额外数据库服务。

三、版本选型说明

组件推荐版本说明
Apache STeVev3(3.0.0,trunk)官方当前开发主线,以源码方式安装;GitHub 无 release tarball、无打包容
Python3.12+(uv 独立 Python 3.12.15)v3 要求 >=3.10,但 Ubuntu 22.04 系统 Python 3.10 链接的 SQLite 3.37 不含 unixepoch(),开选举会报错;uv 独立 Python 自带 SQLite 3.53.1,规避该问题
uv0.13.0官方 quickstart 指定的项目/环境管理器
SQLite≥3.38(venv 内 3.53.1)schema.sql 使用 unixepoch() 函数
asfquart0.1.12STeVe 依赖的 Web 框架(Quart/ASGI)

⚠️ STeVe 的发行渠道只有 GitHub 源码:https://archive.apache.org/dist/steve/ 返回 404,PyPI 上的 steve 是无关同名包(richard 导入工具)。请勿从 PyPI 直接安装。

四、安装方案 A(官方 quickstart:uv 引导安装)

4.1 安装系统依赖

apt-get update -qq
DEBIAN_FRONTEND=noninteractive apt-get install -y \
    python3 python3-pip python3-venv curl wget git sqlite3 net-tools \
    libldap2-dev libsasl2-dev

回显(末行):

Setting up sqlite3 (3.37.2-2ubuntu0.8) ...
3.37.2 2022-01-06 13:25:41 872ba256cbf61d9290b571c0e6d82a20c224ca3ad82971edc46b29818d5dalt1

⚠️ libldap2-dev、libsasl2-dev 必须安装:依赖组中的 python-ldap 编译需要 lber.h 头文件,缺失时报 fatal error: lber.h: No such file or directory(见 8.1)。

4.2 安装 uv

curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
uv --version

回显:

    source $HOME/.local/bin/env (sh, bash, zsh)
    source $HOME/.local/bin/env.fish (fish)
uv 0.13.0 (x86_64-unknown-linux-gnu)

4.3 克隆仓库

git clone --depth 1 https://github.com/apache/steve.git /opt/steve
cd /opt/steve && git log --oneline -1

回显:

3222df7 Rebuild the website with Docusaurus (#110)

v3 全部代码在仓库的 v3/ 目录,后续命令均在该目录下执行。

4.4 创建虚拟环境并同步依赖

cd /opt/steve/v3
uv venv --python 3.12
uv sync --python 3.12

回显:

Using CPython 3.12.15
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate
   ... (依赖安装输出,末尾)
 + watchfiles==1.3.0
 + werkzeug==3.1.9
 + wsproto==1.3.2
 + yarl==1.25.1

核对 venv 内 Python 与 SQLite 版本:

/opt/steve/v3/.venv/bin/python --version
/opt/steve/v3/.venv/bin/python -c "import sqlite3;print('venv sqlite3:',sqlite3.sqlite_version)"

回显:

Python 3.12.15
venv sqlite3: 3.53.1

4.5 初始化数据库

cd /opt/steve/v3/server
sqlite3 steve.db < ../schema.sql
sqlite3 steve.db ".tables"

回显:

election  issue     mayvote   person    vote

五张表:election(选举)、issue(议题)、person(人员)、mayvote(投票资格)、vote(选票,落库即加密)。

4.6 写入人员与管理员账号

STeVe v3 的权限锚点是 person 表中的 pid(选举 owner、可投票人都在此表登记)。官方 quickstart 通过 ASF LDAP 导入(bin/asf-load-ldap.py,需 LDAP 凭据);自建环境可直接 INSERT 写入管理员 admin 与测试选民:

sqlite3 steve.db "INSERT INTO person (pid, name, email) VALUES ('admin', 'Steve Admin', 'admin@steve.apache.org'), ('alice', 'Alice', 'alice@steve.apache.org'), ('bob', 'Bob', 'bob@steve.apache.org'), ('carlos', 'Carlos', 'carlos@steve.apache.org'), ('david', 'David', 'david@steve.apache.org'), ('eve', 'Eve', 'eve@steve.apache.org');"
sqlite3 steve.db "SELECT pid, name, email FROM person;"

回显:

admin|Steve Admin|admin@steve.apache.org
alice|Alice|alice@steve.apache.org
bob|Bob|bob@steve.apache.org
carlos|Carlos|carlos@steve.apache.org
david|David|david@steve.apache.org
eve|Eve|eve@steve.apache.org
sqlite3 steve.db "SELECT count(*) FROM person;"

回显:

6

4.7 创建首个选举

编写 election.yaml(放在 /opt/steve/v3/server/ 下,字段含义见《Apache STeVe 配置参数详解》):

election:
  title: "Demo Election 2026"
  owner_pid: "admin"
  open_at: 1760000000
  close_at: 1880000000

issues:
  - title: "Approve Budget"
    description: "Vote yes/no/abstain on the proposed budget."
    vtype: "yna"

  - title: "Elect Board Members"
    description: "Rank candidates for board seats using STV."
    vtype: "stv"
    kv:
      version: 2
      labelmap:
        a: [alice, "Alice"]
        b: [bob, "Bob"]
        c: [carlos, "Carlos"]
        d: [david, "David"]
      seats: 2

record:
  - admin
  - alice
  - bob
  - carlos
  - david
  - eve

执行创建:

cd /opt/steve/v3/server
uv run bin/create-election.py election.yaml

回显:

INFO:steve.election:Created election[E:0948e72d25]
INFO:__main__:Created election[E:0948e72d25]: "Demo Election 2026" by owner "admin"
INFO:steve.election:Created issue[I:c7e4b8e7ba] in election[E:0948e72d25]
INFO:__main__:Added issue[I:c7e4b8e7ba] to election[E:0948e72d25]
INFO:steve.election:Created issue[I:c156373ef0] in election[E:0948e72d25]
INFO:__main__:Added issue[I:c156373ef0] to election[E:0948e72d25]
INFO:__main__:Added 6 voters to election[E:0948e72d25]
INFO:__main__:Election[E:0948e72d25] fully created from election.yaml

选举 ID(10 位十六进制,如 0948e72d25)由随机生成,每次创建不同。

4.8 启动服务

cd /opt/steve/v3/server
uv run main.py

回显(节选):

[10/12 05:14|INFO|asyncio] <Server sockets=(<asyncio.TransportSocket fd=12, family=2, type=1, proto=0, laddr=('0.0.0.0', 58383)>,)> is serving
[10/12 05:14|INFO|steve] Running on http://0.0.0.0:58383 (CTRL + C to quit)

首次启动前需复制配置:cp config.yaml.example config.yaml(默认带 TLS 证书项,HTTP 明文启动需置空 certfile:/keyfile:,见 7.1)。

4.9 验证服务

端口监听:

ss -tlnp | grep 58383

回显:

tcp   LISTEN 0      0          0.0.0.0:58383      0.0.0.0:*    users:(("python",pid=5980,fd=12))

首页回显:

curl -s -o /dev/null -w "HTTP %{http_code}\n" http://127.0.0.1:58383/
curl -s http://127.0.0.1:58383/ | grep -o "<title>[^<]*</title>"

回显:

HTTP 200
<title>Apache STeVe: Home</title>

投票与计票功能验证(开启选举、6 人投票、关闭并计票):

uv run bin/tally.py --election-id 0948e72d25 --output text

回显(选举经 Python API 开启、10 张选票投出并关闭后):

INFO:__main__:Tallying 2 issues ...
Issue c156373ef0: Elect Board Members (stv)
Result: Alice                                    elected
        Bob                                      elected
        Carlos                                   not elected
        David                                    not elected
Details: {'candidates': {'a': True, 'b': True, 'c': False, 'd': False}, 'version': 2, 'labelmap': {'a': ['alice', 'Alice'], 'b': ['bob', 'Bob'], 'c': ['carlos', 'Carlos'], 'd': ['david', 'David']}, 'seats': 2, 'votestrings': ['b,c,a', 'b,a,d', 'd,c,b', 'a,c,b', 'a,b,c']}
----------------------------------------
Issue c7e4b8e7ba: Approve Budget (yna)
Result: Yes:        3
        No:         1
        Abstain:    1
Details: {'y': 3, 'n': 1, 'a': 1}
----------------------------------------

STV 2 席位 Alice、Bob 当选,yna 议题 3 赞成/1 反对/1 弃权,与投放选票一致。

4.10 systemd 托管

创建 /etc/systemd/system/steve.service:

[Unit]
Description=Apache STeVe v3 voting server
Documentation=https://github.com/apache/steve
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/steve/v3/server
ExecStart=/opt/steve/v3/.venv/bin/python main.py
Restart=on-failure
RestartSec=5
Environment=PYTHONUNBUFFERED=1

[Install]
WantedBy=multi-user.target

启用并验证:

systemctl daemon-reload
systemctl enable --now steve.service
systemctl is-active steve.service

回显:

Created symlink /etc/systemd/system/multi-user.target.wants/steve.service → /etc/systemd/system/steve.service.
active

状态详情:

systemctl --no-pager -l status steve.service

回显:

● steve.service - Apache STeVe v3 voting server
     Loaded: loaded (/etc/systemd/system/steve.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2026-10-12 05:16:50 UTC; 8s ago
       Docs: https://github.com/apache/steve
   Main PID: 6997 (python)
      Tasks: 5 (limit: 5753)
     Memory: 53.7M
        CPU: 1.274s
     CGroup: /system.slice/steve.service
             └─6997 /opt/steve/v3/.venv/bin/python main.py

Oct 12 05:16:51 dfb8320a08ff python[6997]: [10/12 05:16|INFO|asfpy.twatcher] Watching: /opt/steve/v3/server/templates/settings.ezt

五、安装方案 B(备用:pip 引导 uv)

适用于不便执行远程安装脚本的场景,用 pip 安装 uv 后复用方案 A 的全部步骤。

5.1 安装系统依赖

与方案 A 的 4.1 完全一致(同一条 apt 命令与回显)。

5.2 pip 安装 uv

pip3 install uv
uv --version

回显(节选):

WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
uv 0.13.0 (x86_64-unknown-linux-gnu)
pip3 show uv | head -3

回显:

Name: uv
Version: 0.13.0
Summary: An extremely fast Python package and project manager, written in Rust.

5.3 后续步骤

从 4.3(克隆仓库)起与方案 A 完全一致,直至 4.10(systemd 托管)。

六、多方案对比与选择建议

维度方案 A(uv 脚本引导)方案 B(pip 引导 uv)
版本uv 0.13.0uv 0.13.0
是否需联网是(astral.sh + GitHub + PyPI)是(PyPI + GitHub)
安装方式官方安装脚本pip 包
uv 安装位置/root/.local/bin/uv/usr/local/bin/uv
服务名steve.servicesteve.service
监听端口5838358383
优点官方推荐路径,自更新方便复用既有 pip 环境,无需访问 astral.sh
缺点需访问 astral.shpip 装进系统 Python 环境,升级随 pip

💡 选择建议:全新机器走方案 A;受网络策略限制只能访问 PyPI 的环境走方案 B。两方案最终都生成同一套 /opt/steve 目录与 venv,后续运维完全一致。

七、配置说明

7.1 config.yaml

配置文件位于 /opt/steve/v3/server/config.yaml,由模板拷贝:

cd /opt/steve/v3/server
cp config.yaml.example config.yaml

HTTP 明文模式(本指南 systemd 托管采用):

server:
    port: 58383
    certfile:
    keyfile:

db: steve.db

7.2 TLS 证书配置

certfile/keyfile 填相对 server/certs/ 目录的文件名(或绝对路径)即启用 HTTPS。用 openssl 生成自签证书:

cd /opt/steve/v3/server/certs
openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes \
    -keyout server.key -out server.crt -subj "/CN=localhost"
openssl x509 -in server.crt -noout -subject -dates

回显:

subject=CN = localhost
notBefore=Oct 12 05:17:19 2026 GMT
notAfter=Oct 12 05:17:19 2027 GMT

config.yaml 改为 certfile: server.crt、keyfile: server.key 并重启服务后:

curl -sk -o /dev/null -w "HTTPS %{http_code}\n" https://127.0.0.1:58383/
curl -sk https://127.0.0.1:58383/ | grep -o "<title>[^<]*</title>"

回显:

HTTPS 200
<title>Apache STeVe: Home</title>

💡 官方 certs/README 推荐 mkcert 生成本地信任证书;openssl 等任何标准工具链同样可用。

7.3 ASGI 运行形态

main.py 内置 Hypercorn ASGI 入口,生产可改为独立 ASGI 服务器托管:

cd /opt/steve/v3/server
uv run python -m hypercorn main:steve_app

回显(节选):

[10/12 05:19|INFO|main]  ** Run-mode: ASGI
[2026-10-12 05:20:20 +0000] [7197] [INFO] Running on http://127.0.0.1:8000 (CTRL + C to quit)
[10/12 05:20|INFO|hypercorn.error] Running on http://127.0.0.1:8000 (CTRL + C to quit)

Hypercorn 默认绑定 127.0.0.1:8000,改绑端口用 --bind 0.0.0.0:58383。gunicorn 为 WSGI 服务器,不适用于 STeVe(asfquart 基于 Quart/asyncio,必须 ASGI)。

八、常见问题(FAQ)

8.1 uv sync 报 fatal error: lber.h: No such file or directory

错误回显(节选):

In file included from Modules/LDAPObject.c:8:
Modules/constants.h:7:10: fatal error: lber.h: No such file or directory
    7 | #include "lber.h"
      |          ^~~~~~~~
compilation terminated.
hint: `python-ldap` (v3.0.0) was included because `apache-steve:dev` (v3.0.0) depends on `python-ldap`

定位:dev 依赖组的 python-ldap 编译需要 OpenLDAP 头文件。 解决:apt-get install -y libldap2-dev libsasl2-dev 后重新 uv sync。

8.2 开选举报 no such function: unixepoch

错误回显:

sqlite3.OperationalError: no such function: unixepoch

定位:schema.sql 的触发器使用 unixepoch(),要求 SQLite ≥3.38;Ubuntu 22.04 系统 Python 3.10 链接的 libsqlite3 为 3.37.2。 解决:用 uv 独立 Python 3.12 重建 venv(自带 SQLite 3.53.1):

cd /opt/steve/v3
rm -rf .venv
uv venv --python 3.12
uv sync --python 3.12

8.3 git clone 报 GnuTLS recv error (-54) 或 Connection refused

定位:GitHub 连接偶发抖动。 解决:重试 git clone 即可;多次失败时检查到 github.com:443 的网络策略。

8.4 端口占用

错误回显(Hypercorn 形态):

OSError: [Errno 98] Address already in use

解决:ss -tlnp | grep 58383 找到占用进程;同一台机器上避免 systemd 服务与手工进程重复启动(本指南实测中 systemd 的 steve.service 存活时再以同端口起 Hypercorn 即复现此错)。

8.5 访问受限页跳转 /auth

未登录访问 /voter、/admin 等页面时,302 跳转至 asfquart 的登录端点:

# curl -s http://127.0.0.1:58383/voter | head -c 300
<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/auth?login=/voter?">/auth?login=/voter?</a>. If not, click the link.

说明:STeVe v3 无本地口令登录,认证经 asfquart 对接 Apache OAuth(部署到 ASF 基础设施后自动生效);自建环境通过 person 表 pid 与 Python API 完成数据层管理(选举创建、开闭、计票),Web 端登录仅在使用 ASF OAuth 时可用。本文示例管理账号 admin(演示口令 q1w2e3r4)即数据层 owner_pid 的取值。

九、卸载方式

systemctl disable --now steve.service   # 停止并取消自启
rm -f /etc/systemd/system/steve.service  # 删除服务单元
systemctl daemon-reload
rm -rf /opt/steve                        # 删除源码、venv 与数据库
apt-get remove -y libldap2-dev libsasl2-dev  # 可选:清理编译头文件

十、参考链接