Pagure 5.14.1 Ubuntu 22.04 安装指南
在 Ubuntu 22.04.5 LTS 上从零安装 Pagure 5.14.1:源码部署 + Python 虚拟环境钉版依赖 + PostgreSQL/Redis/gitolite3 + systemd 与 Nginx 反向代理,含注册激活、SSH 推送端到端验证、离线 wheel 方案与 8 类实测排障。
Pagure 5.14.1 Ubuntu 22.04 安装指南
目标:在 Ubuntu 22.04 服务器上从零安装 Pagure 5.14.1(Fedora 社区开源的 Git 托管与协作平台),完成 Web 服务、后台任务队列、gitolite3 SSH 授权链、数据库与 Nginx 反向代理的完整部署。全文命令与回显已在 Ubuntu 22.04.5 LTS 环境实测通过(含 Web 访问、REST API、用户注册激活与 SSH 推送端到端验证)。
一、环境信息
本指南所有操作在以下环境实测完成,读者按相同版本部署可获得一致的回显。
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- Pagure:5.14.1(官方 git 仓库最新 tag,发布于 2024-05-24)
- Python:3.10.12(系统自带)
- PostgreSQL:14.24(Ubuntu 仓库 14+238)
- Redis:6.0.16
- gitolite3:3.6.12-1(Debian/Ubuntu 打包版)
- git:2.34.1
- Nginx:1.18.0
- Gunicorn:26.2.0(Pagure 虚拟环境内安装)
- Celery:5.6.3(Pagure 虚拟环境内安装)
- 关键路径:源码与虚拟环境
/opt/pagure,配置文件/etc/pagure/pagure.cfg,git 仓库与 gitolite 主目录/srv/git,日志目录/var/log/pagure - 监听端口:HTTP 80(Nginx 反向代理)、SSH 22(gitolite3 授权)
- 示例管理账号:
admin,密码q1w2e3r4
💡 技术拓展:Git 托管平台的「写权限授权后端」不止一种实现。常见方案对比——
授权后端 原理 适用场景 gitolite3 独立 Perl 网关,conf 文件集中管理 ACL 多仓库、批量权限管理(Pagure 默认) AuthorizedKeysCommand sshd 直接查询数据库动态出公钥 不想引入 gitolite 的轻量场景 应用层 CGI/网关 所有 git 请求经应用进程校验 需要应用层细粒度钩子 Pagure 5.14.1 支持
gitolite3(默认)、pagure、pagure_authorized_keys三种GIT_AUTH_BACKEND。本指南采用官方默认的 gitolite3,理由是它经过 Fedora 生产环境多年验证且与 Pagure 的 ACL 模型集成最完整。
二、前置条件
安装 Pagure 前,需要先确认系统资源、软件源与依赖组件就绪。Pagure 是 Flask + Celery + PostgreSQL + Redis + gitolite3 的多进程架构,前置依赖比单进程应用多,逐项列清如下。
2.1 硬件与系统要求
- CPU:2 核以上(Web 4 worker + Celery worker 并行运行)
- 内存:4 GB 以上(实测部署后 Python 进程组常驻约 600 MB,PostgreSQL 与 Redis 另计)
- 磁盘:10 GB 以上(含系统、依赖与 git 仓库存储;git 仓库大小随项目增长)
- 需要 systemd 的 Linux 真机(本指南使用 systemd 托管 pagure-web 与 pagure-worker 两个服务单元)
2.2 软件依赖与架构说明
Pagure 5.14.1 各组件职责:
| 组件 | 作用 | 是否必装 |
|---|---|---|
| PostgreSQL | 存用户、项目、工单、PR 等元数据(54 张表) | 必装 |
| Redis | Celery 消息队列后端与会话缓存 | 必装 |
| gitolite3 | SSH 协议 git 访问的授权网关 | 必装(默认授权后端) |
| Nginx | HTTP 反向代理,转发到 Gunicorn Unix 套接字 | 必装 |
| Python 虚拟环境 | 隔离 Pagure 及其 64 个 Python 依赖 | 必装 |
更新软件源并安装系统级依赖:
apt update
apt install -y python3-venv python3-dev python3-gdbm build-essential libffi-dev \
git gitolite3 postgresql postgresql-contrib redis nginx curl
实测环境安装后的版本核对回显:
dpkg -l build-essential python3-venv python3-dev libffi-dev gitolite3 postgresql redis-server nginx | grep "^ii" | awk '{print $2, $3}'
输出节选(实测):
build-essential 12.9ubuntu3
gitolite3 3.6.12-1
libffi-dev:amd64 3.4.2-4
nginx 1.18.0-6ubuntu14.22
postgresql 14+238
python3-dev 3.10.6-1~22.04.1
python3-venv 3.10.6-1~22.04.1
redis-server 5:6.0.16-1ubuntu1.2
⚠️ 注意:
python3-gdbm必须安装。Pagure 依赖链中的binaryornot等包运行时会导入 Python 的 gdbm 模块,缺它时启动报ModuleNotFoundError: No module named '_gdbm'(虚拟环境与系统共享 lib-dynload 目录,故需装系统包而非 pip 包)。这是实测踩过的坑,见「八、常见问题」8.1。
三、版本选型说明
选型前先确认 Pagure 的版本获取渠道与当前最新稳定版。Pagure 官方主仓库在 pagure.io,GitHub 上 pagure/pagure 只是同步副本(无 Releases 页面,不能从它下载发行包),版本必须以官方 git 仓库 tag 为准。
查询官方仓库 tag 列表(按时间排序取最新):
git ls-remote --tags --sort=-v:refname https://pagure.io/pagure.git | head -6
输出节选(实测,查询日期 2026-10-28):
d29ed2cb931832dd5e6934326d7738ae3961ddda refs/tags/5.9.0
007e18b62e34208a1ea759388507734a01bb8150 refs/tags/5.9.1
7b5cb091c9fcaf9d91f95629cb5990c05f3f148c refs/tags/5.7.8
6ed09b5f7b2ab0d6dc27b253f8de0acc75974887 refs/tags/5.7.9
1f3f0c8ef222fe38346c39d4c300c6c9872f5609 refs/tags/5.8.1
a4c88307fc601ce247d4818ca0039267499782f9 refs/tags/5.8
💡 说明:
git ls-remote的--sort输出按版本号倒序,5.14.1 为最新 tag。本指南统一安装 5.14.1(2024-05-24 发布,官方仓库 5.x 系列最新稳定版)。截至 2026-10-28,官方仓库累计 284 个 tag,项目处于稳定维护期、无破坏性升级压力,生产可放心锁定 5.14.1。
版本核对方法(安装完成后执行):
/opt/pagure/venv/bin/python -c "import pagure; print(pagure.__version__)"
实测输出:
5.14.1
四、在线安装方案(源码部署 + Python 虚拟环境,官方推荐路径)
4.1 方案说明
这是 Pagure 官方文档(doc/install.rst)推荐的部署形态:从官方 git 仓库拉取 5.14.1 源码,用独立 Python 虚拟环境安装依赖,PostgreSQL 存元数据、Redis 做任务队列、gitolite3 管 SSH 授权,Gunicorn 承载 Web 进程,Nginx 做反向代理。全流程在可联网的 Ubuntu 22.04 机器上完成。
4.2 安装系统依赖
见「二、前置条件」2.2 节的 apt 安装命令,此处不再重复。
4.3 安装并初始化 PostgreSQL
Ubuntu 仓库的 postgresql 包安装后自动启动,并创建名为 main 的实例。为 Pagure 创建专用数据库账号与库(示例口令 q1w2e3r4,生产环境请替换为强口令):
su postgres -c "psql -c \"CREATE USER pagure WITH PASSWORD 'q1w2e3r4';\""
su postgres -c "createdb -O pagure pagure"
验证数据库可登录:
PGPASSWORD=q1w2e3r4 psql -h localhost -U pagure -d pagure -c "SELECT version();" | head -3
实测输出:
psql (14.24 (Ubuntu 14.24-0ubuntu0.22.04.1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
4.4 确认 Redis 就绪
Redis 包安装后自动以 systemd 服务运行,Pagure 将其作为 Celery broker 与缓存。确认其监听 6379:
redis-cli ping
实测输出:
PONG
4.5 创建 git 用户并初始化 gitolite3
gitolite3 要求一个专用系统用户(约定名 git)作为所有 SSH git 访问的入口账号,Pagure 的 git 仓库也都存放在它的主目录下。
创建 git 用户并规划目录:
useradd -m -d /srv/git -s /bin/bash git
⚠️ 注意:若系统已有
git用户(id git可查),改用usermod -d /srv/git -m git迁移其主目录即可,不要重复创建。
生成 Pagure 管理员的 SSH 密钥(该密钥将绑定为 Pagure 首个管理员账号的 git 访问凭据):
ssh-keygen -t ed25519 -f ~/.ssh/pagure_admin -N '' -C 'admin@pagure.lab'
实测输出:
Generating public/private ed25519 key pair.
Your identification has been saved in /root/.ssh/pagure_admin
Your public key has been saved in /root/.ssh/pagure_admin.pub
用该公钥初始化 gitolite3(-pk 后跟公钥文件,密钥文件名会成为 gitolite 里的用户名):
install -o git -g git -m 644 ~/.ssh/pagure_admin.pub /tmp/admin.pub
su - git -c "gitolite setup -pk /tmp/admin.pub"
实测输出:
Initialized empty Git repository in /srv/git/repositories/gitolite-admin.git/
Initialized empty Git repository in /srv/git/repositories/testing.git/
WARNING: /srv/git/.ssh/authorized_keys missing; creating a new one
Done
💡 说明:Debian/Ubuntu 打包的 gitolite3 与上游源码版有参数差异——初始化用
gitolite setup -pk <公钥文件>,上游文档里的-kc参数在 Debian 版会报Unknown option;且-a(管理员组)与-pk互斥,同时给会直接打印 usage。照本节命令执行即可。
验证 gitolite 初始化成功:
su - git -c "gitolite compile && echo COMPILE_OK"
实测输出:
COMPILE_OK
4.6 获取 Pagure 5.14.1 源码
mkdir -p /opt/pagure /etc/pagure /var/log/pagure
cd /opt/pagure
git clone https://pagure.io/pagure.git .
git checkout 5.14.1
克隆与检出成功后核对源码版本:
git -C /opt/pagure describe --tags
git -C /opt/pagure log --oneline -1
实测输出:
5.14.1
6b06ac58 docs(changelog): Add links to related pull requests to security fixes in 5.14.1
💡 说明:Pagure 不发布 PyPI 包(PyPI 上查询返回 404),也不提供 GitHub Releases 发行包,git 仓库 checkout tag 是唯一的版本化安装方式。若服务器无法直连 pagure.io,可先在有网机器
git clone后打包传输,或使用「五、离线安装方案」。
4.7 创建虚拟环境并安装 Python 依赖
Pagure 5.14.1 的依赖清单在源码 requirements.txt 中。直接安装会拉到过新的版本并报错(Pagure 编写时基于 SQLAlchemy 1.4 / Flask 2.2 时代的 API),因此采用「先装钉版核心依赖、再装其余依赖」的顺序,版本号为实测确定的可用组合:
python3 -m venv /opt/pagure/venv
/opt/pagure/venv/bin/pip install -U pip setuptools'<81' wheel
/opt/pagure/venv/bin/pip install psycopg2-binary
/opt/pagure/venv/bin/pip install pygit2==1.13.3 sqlalchemy==1.4.54 wtforms==2.3.3 \
flask-wtf==1.2.1 flask==2.2.5 werkzeug==2.3.8 alembic==1.13.2 bleach==5.0.1
/opt/pagure/venv/bin/pip install -r /opt/pagure/requirements.txt
/opt/pagure/venv/bin/pip install -e /opt/pagure
安装完成后核对关键包版本:
/opt/pagure/venv/bin/python -c "import pagure, flask, pygit2, sqlalchemy; print('pagure', pagure.__version__, '| flask', flask.__version__, '| pygit2', pygit2.__version__, '| sqlalchemy', sqlalchemy.__version__)"
实测输出:
pagure 5.14.1 | flask 2.2.5 | pygit2 1.13.3 | sqlalchemy 1.4.54
⚠️ 注意:不要省略钉版步骤。实测中,
pygit2装到 1.14+ 会报No module named 'pygit2.remote';sqlalchemy装到 2.x 会报cannot import name 'relation' from 'sqlalchemy.orm'(Pagure 模型层model.py:32);flask装到 3.x 会报cannot import name 'Markup' from 'flask';wtforms装到 3.x 会报no attribute 'Required';setuptools装到 81+ 会报No module named 'pkg_resources'。上述报错与对应钉版均为实测结论,详见「八、常见问题」。
4.8 初始化数据库表结构
Pagure 源码根目录自带 createdb.py,读取配置里的 DB_URL 建全部表:
cd /opt/pagure
PAGURE_CONFIG=/etc/pagure/pagure.cfg /opt/pagure/venv/bin/python createdb.py
💡 说明:脚本成功时无输出、退出码 0。此时配置文件
/etc/pagure/pagure.cfg尚未创建的话,先按 4.9 节写好再回来执行本步(两节顺序可对调,但 createdb 必须在配置文件就绪之后)。
验证表数量:
PGPASSWORD=q1w2e3r4 psql -h localhost -U pagure -d pagure -t -A -c "SELECT count(*) FROM information_schema.tables WHERE table_schema='public';"
实测输出:
54
4.9 编写 pagure.cfg 主配置
Pagure 通过 PAGURE_CONFIG 环境变量指定的 Python 配置文件生效,未指定时尝试读 /etc/pagure/pagure.cfg。创建最小可运行配置(每项含义见注释;完整参数逐项详解见《Pagure 5.14.1 配置参数详解》):
# /etc/pagure/pagure.cfg
from datetime import timedelta
ADMIN_SESSION_LIFETIME = timedelta(minutes=20)
ENABLE_TICKETS = True # 开启工单
ENABLE_DOCS = True # 开启项目文档 git 仓库
PRIVATE_PROJECTS = True # 允许私有项目
SECRET_KEY = 'pagure-lab-secret-20261028' # Flask 会话密钥,生产换成长随机串
DB_URL = 'postgresql://pagure:q1w2e3r4@localhost:5432/pagure'
ADMIN_GROUP = ['pagure-admin'] # 管理员组
PAGURE_ADMIN_USERS = ['admin'] # 硬编码全局管理员
EMAIL_SEND = False # 本指南关闭外发邮件(离线激活,见 7.1)
EMAIL_ERROR = 'root@localhost'
SMTP_SERVER = 'localhost'
SMTP_PORT = 25
SMTP_SSL = False
SMTP_USERNAME = None
SMTP_PASSWORD = None
FROM_EMAIL = 'pagure@pagure.lab'
DOMAIN_EMAIL_NOTIFICATIONS = 'pagure.lab'
SALT_EMAIL = 'pagure-lab-salt-20261028'
APP_URL = 'http://localhost/' # 站点对外 URL,生产换成实际域名
DOC_APP_URL = None # 关闭项目文档站点功能
GIT_URL_SSH = 'ssh://git@localhost/' # git SSH 克隆地址前缀
GIT_URL_GIT = 'git://localhost/'
GIT_FOLDER = '/srv/git/repositories' # git 仓库根目录
REMOTE_GIT_FOLDER = '/srv/git/remotes' # 远程 PR 的 clone 存放处
GITOLITE_CONFIG = '/srv/git/gitolite-admin/conf/gitolite.conf'
GITOLITE_HOME = '/srv/git'
GITOLITE_VERSION = 3
GITOLITE_KEYDIR = '/srv/git/gitolite-admin/keydir'
GL_RC = '/srv/git/.gitolite.rc'
GL_BINDIR = '/usr/bin'
GIT_AUTH_BACKEND = 'gitolite3'
HTTP_REPO_ACCESS_GITOLITE = '/usr/share/gitolite3/gitolite-shell'
SSH_COMMAND_NON_REPOSPANNER = (
[
"/usr/share/gitolite3/gitolite-shell",
"%(username)s",
"%(cmd)s",
"%(reponame)s",
],
{},
)
ITEM_PER_PAGE = 50
MAX_CONTENT_LENGTH = 4 * 1024 * 1024
SHORT_LENGTH = 6
IP_ALLOWED_INTERNAL = ['127.0.0.1', 'localhost', '::1']
EVENTSOURCE_SOURCE = None
EVENTSOURCE_PORT = 8080
WEBHOOK = False
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
REDIS_DB = 0
PAGURE_AUTH = 'local' # 本地账号密码认证
SESSION_COOKIE_SECURE = False
SESSION_COOKIE_NAME = 'pagure'
CHECK_SESSION_IP = True
APPLICATION_ROOT = '/'
OLD_VIEW_COMMIT_ENABLED = False
逐项说明(关键项):
SECRET_KEY:Flask 会话签名密钥,泄露等于伪造任意会话;生产用python3 -c "import secrets;print(secrets.token_hex(32))"生成。DB_URL:SQLAlchemy 连接串,格式postgresql://用户:口令@主机:端口/库名。PAGURE_ADMIN_USERS:无需任何组关系即拥有全站管理权限的用户名列表,配合pagure-adminCLI 使用。GITOLITE_*与GL_*:声明 gitolite 的 conf、keydir、rc、可执行文件位置,五个路径必须与 4.5 节 gitolite 初始化的实际落点一致。EMAIL_SEND = False:关闭邮件后注册流程不发邮件,需手动到数据库取激活 token(见 7.1);生产接好 SMTP 后改 True。
4.10 初始化 gitolite 管理配置
gitolite 靠 gitolite-admin 工作副本里的 conf 文件管理 ACL,Pagure 会直接写这个文件。Debian 版 gitolite3 默认把管理副本放在 ~/.gitolite(上游默认 ~/gitolite-admin),而 Pagure 按上游约定写 /srv/git/gitolite-admin,因此必须在 .gitolite.rc 里显式对齐两个路径:
sed -i '/^%RC = (/a\ GL_REPO_BASE => '"'"'/srv/git/repositories'"'"',\n GL_ADMIN_BASE => '"'"'/srv/git/gitolite-admin'"'"',' /srv/git/.gitolite.rc
head -5 /srv/git/.gitolite.rc
实测输出:
#!/usr/bin/perl
use warnings;
use strict;
our %RC = (
GL_REPO_BASE => '/srv/git/repositories',
GL_ADMIN_BASE => '/srv/git/gitolite-admin',
让 Pagure 生成 gitolite ACL 配置并编译(pagure-admin 是 Pagure 的管理 CLI,位于虚拟环境 bin 目录):
PAGURE_CONFIG=/etc/pagure/pagure.cfg /opt/pagure/venv/bin/pagure-admin refresh-gitolite
按提示回答两次 y,实测输出节选(实测首尾):
Please note that you have not selected a project or --all. Do you want to recompile the existing config file?
Do you want to continue? [y/N]y
Do you want to re-generate the gitolite.conf file for group: None and project: None?
Do you want to continue? [y/N]y
Gitolite ACLs updated
验证 gitolite 归属与 conf 落盘:
su - git -c "git -C /srv/git/gitolite-admin log --oneline -1"
实测输出:
6bd19a6 refresh
⚠️ 注意:
refresh-gitolite会触发 gitolite 编译并重写/srv/git/.ssh/authorized_keys,必须以 git 用户运行(本节用虚拟环境路径直接执行即可,前提是当前 shell 用户对/srv/git有写权限;root 执行会把它写成 root 属主,随后 worker 的编译任务将报FATAL: open /srv/git/.ssh/authorized_keys failed: Permission denied,排障过程见「八、常见问题」8.4)。
4.11 systemd 托管(pagure-web 与 pagure-worker)
Pagure 运行期有两个常驻进程:Gunicorn 承载的 Web 应用(pagure-web)与 Celery 承载的异步任务 worker(pagure-worker,负责建仓库、生成 gitolite conf、发通知等)。分别编写单元文件。
Web 服务单元:
# /etc/systemd/system/pagure-web.service
[Unit]
Description=Pagure web (gunicorn)
After=network.target postgresql.service redis-server.service
[Service]
Type=simple
User=git
Group=git
WorkingDirectory=/opt/pagure
ExecStart=/opt/pagure/venv/bin/gunicorn --workers 4 \
--env PAGURE_CONFIG=/etc/pagure/pagure.cfg \
--access-logfile /var/log/pagure/access_web.log \
--error-logfile /var/log/pagure/error_web.log \
--bind unix:/tmp/pagure_web.sock \
"pagure.flask_app:create_app()"
Restart=on-failure
[Install]
WantedBy=multi-user.target
后台任务服务单元:
# /etc/systemd/system/pagure-worker.service
[Unit]
Description=Pagure worker (celery)
After=network.target postgresql.service redis-server.service
[Service]
Type=simple
User=git
Group=git
Environment="PAGURE_CONFIG=/etc/pagure/pagure.cfg"
ExecStart=/opt/pagure/venv/bin/celery -A pagure.lib.tasks worker --loglevel=INFO -n worker
Restart=on-failure
[Install]
WantedBy=multi-user.target
修日志目录属主、重载 systemd 并启动两个服务(每行只操作一个单元):
chown -R git:git /var/log/pagure /srv/git /opt/pagure
systemctl daemon-reload
systemctl enable pagure-web
systemctl enable pagure-worker
systemctl start pagure-web
systemctl start pagure-worker
确认两个服务状态:
systemctl is-active pagure-web
systemctl is-active pagure-worker
实测输出(两次执行各返回一行):
active
active
4.12 Nginx 反向代理
Nginx 监听 80,静态请求直接回文件、动态请求转发到 Gunicorn 的 Unix 套接字:
# /etc/nginx/sites-available/pagure.conf
server {
listen 80;
server_name _;
access_log /var/log/pagure/access_web.log;
error_log /var/log/pagure/error_web.log;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://unix:/tmp/pagure_web.sock;
}
location /static/ {
alias /opt/pagure/pagure/static/;
try_files $uri @pagure;
}
location @pagure {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://unix:/tmp/pagure_web.sock;
}
}
启用站点并重载:
ln -sf /etc/nginx/sites-available/pagure.conf /etc/nginx/sites-enabled/pagure.conf
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginx
实测输出(nginx -t):
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
4.13 安装验证(Web / API / 注册登录 / SSH 推送)
分四层验证,全部实测通过即部署完成。
第一层,Web 服务状态与首页:
systemctl is-active pagure-web
curl -s -o /dev/null -w "%{http_code}\n" http://localhost/
curl -s http://localhost/ | grep -o "<title>[^<]*</title>" | head -1
实测输出:
active
200
<title>Home - Pagure</title>
第二层,REST API(Pagure 自带 API 版本端点与项目列表):
curl -s http://localhost/api/0/version
echo
curl -s "http://127.0.0.1/api/0/projects?short=1"
实测输出(第二条为创建 public-demo 项目后查询,输出节选(实测首尾),中间 JSON 键序保留原样):
{"version":"0.31"}
{"args":{"fork":null,"namespace":null,"owner":null,"page":1,"pattern":null,"per_page":20,"short":true,"tags":[],"username":null},"pagination":{"first":"http://localhost/api/0/projects?per_page=20&short=1&page=1","last":"http://localhost/api/0/projects?per_page=20&short=1&page=1","next":null,"page":1,"pages":1,"per_page":20,"prev":null},"projects":[{"description":"Public demo project","fullname":"public-demo","name":"public-demo","namespace":null}],"total_projects":1}
💡 说明:
{"version":"0.31"}返回的是 API 协议版本 0.31,不是 Pagure 软件版本(软件版本用 4.7 节的pagure.__version__查询)。
第三层,注册管理员账号并激活(详见「七、配置说明」7.1),登录后创建项目 public-demo。
第四层,SSH 推送端到端验证——在管理员机器上克隆并推送:
mkdir -p /tmp/pd && cd /tmp/pd
git init
git remote add origin ssh://git@localhost/public-demo.git
echo "# public demo" > README.md
git add README.md
git commit -m "first commit"
git branch -M main
GIT_SSH_COMMAND="ssh -i ~/.ssh/pagure_admin" git push origin main
实测输出(带 -v 级别日志已省略,仅保留 gitolite 授权与推送结果;首次推送前的 hook 环境问题见 8.3):
To ssh://localhost/public-demo.git
* [new branch] main -> main
推送成功后从 API 侧确认分支与默认分支:
curl -s http://localhost/api/0/public-demo/git/branches
实测输出:
{"branches":["main"],"default":"main","total_branches":1}
再确认 gitolite 已把该账号授权到全部关联仓库:
ssh -i ~/.ssh/pagure_admin git@localhost info
实测输出:
hello admin, this is git@pagure running gitolite3 3.6.12-1 (Debian) on git 2.34.1
R W admin/demo-project
R W docs/admin/demo-project
R W docs/public-demo
R W public-demo
R W requests/admin/demo-project
R W requests/public-demo
R W tickets/admin/demo-project
R W tickets/public-demo
💡 说明:
info列出的docs/、tickets/、requests/前缀仓库是 Pagure 为每个项目自动创建的文档、工单、PR 元数据仓库,属正常现象。日常运维命令(分支管理、只读切换、备份等)见《Pagure 5.14.1 常用 CLI 与运维命令详解》。
五、离线安装方案(wheel 离线包,断网服务器)
5.1 方案说明
生产环境常见「目标服务器无外网」场景。方案是把全部 Python 依赖以 wheel 形式在有网机器一次性下载,连同源码打成离线包传输;目标机用 pip --no-index 纯离线安装。Python 侧全流程实测通过;系统级 apt 依赖(postgresql/gitolite3/nginx 等)仍需目标机有本地 apt 源或提前离线下载 deb 包。
5.2 有网机器:准备离线包
在有网的 Ubuntu 22.04 机器上,先按「四、在线安装方案」4.2–4.6 装好系统依赖与源码,然后导出钉版依赖清单并下载 wheel:
/opt/pagure/venv/bin/pip freeze | grep -v -i pagure > /tmp/offline-req.txt
/opt/pagure/venv/bin/pip download -d /opt/wheelhouse -r /tmp/offline-req.txt
/opt/pagure/venv/bin/pip download -d /opt/wheelhouse wheel
实测输出节选(实测首尾):
Saved ./opt/wheelhouse/WTForms-2.3.3-py2.py3-none-any.whl
Successfully downloaded alembic amqp vine arrow async-timeout bcrypt billiard binaryornot bleach blinker celery click certifi cffi chardet charset-normalizer click-didyoumean click-plugins click-repl cryptography defusedxml dnspython docutils email-validator exceptiongroup Flask Flask-WTF greenlet gunicorn idna itsdangerous Jinja2 kitchen kombu Mako Markdown MarkupSafe munch packaging pillow prompt_toolkit psutil psycopg2-binary pycparser pygit2 python-dateutil python-openid-cla python-openid-teams python3-openid redis requests urllib3 six SQLAlchemy straight.plugin tomli typing_extensions tzdata tzlocal wcwidth webencodings Werkzeug whitenoise WTForms
把源码与 wheelhouse 打成离线包(实测约 122 MB):
tar -czf /opt/pagure-offline-bundle.tar.gz -C /opt pagure wheelhouse
ls -lh /opt/pagure-offline-bundle.tar.gz | awk '{print $5}'
实测输出:
122M
将 /opt/pagure-offline-bundle.tar.gz 用任意介质拷贝到目标机 /opt/ 下。
5.3 目标机:离线安装
目标机先按 4.2 装好 apt 依赖(需本地源),然后解包并离线装 Python 侧:
tar -xzf /opt/pagure-offline-bundle.tar.gz -C /opt
python3 -m venv /opt/pagure/venv
/opt/pagure/venv/bin/pip install -U pip setuptools'<81' wheel
/opt/pagure/venv/bin/pip install --no-index --find-links=/opt/wheelhouse \
psycopg2-binary pygit2==1.13.3 sqlalchemy==1.4.54 wtforms==2.3.3 \
flask-wtf==1.2.1 flask==2.2.5 werkzeug==2.3.8 alembic==1.13.2 bleach==5.0.1 wheel
/opt/pagure/venv/bin/pip install --no-index --find-links=/opt/wheelhouse -r /tmp/offline-req.txt
/opt/pagure/venv/bin/pip install --no-index --no-build-isolation -e /opt/pagure
⚠️ 注意:
/tmp/offline-req.txt随离线包一起从有网机器拷贝。最后一步安装 Pagure 自身必须带--no-index --no-build-isolation,且目标虚拟环境里要先装好wheel(缺它时报error: invalid command 'bdist_wheel',为实测踩坑结论)。
离线安装的版本核对:
/opt/pagure/venv/bin/python -c "import pagure, flask, pygit2; print('pagure', pagure.__version__, '| flask', flask.__version__, '| pygit2', pygit2.__version__)"
实测输出:
pagure 5.14.1 | flask 2.2.5 | pygit2 1.13.3
5.4 与在线方案的差异
离线包路径只替代「四、在线安装方案」的 4.7 节(Python 依赖获取方式),其余步骤(数据库初始化、pagure.cfg、gitolite、systemd、Nginx、验证)与在线方案完全一致,按 4.8–4.13 继续执行即可。
六、方案对比与选择建议
| 维度 | 方案 A:在线源码部署 | 方案 B:离线 wheel 包部署 |
|---|---|---|
| 版本 | 5.14.1(git tag) | 5.14.1(同一源码打包) |
| 是否需联网 | 是(git clone + pip) | 仅准备阶段需要,目标机零外网 |
| 依赖确定性 | pip 实时解析,需按 4.7 钉版 | wheel 清单固化,版本与实测环境完全一致 |
| 升级方式 | 重新 checkout tag 后装依赖 | 重新做离线包全量替换 |
| 适用场景 | 可联网的测试/生产服务器 | 内网隔离的生产服务器 |
选择建议:服务器能联网优先用方案 A(步骤最少);有严格外网隔离要求、或要求多台服务器依赖版本完全一致时用方案 B——离线包里固化的是本指南实测通过的那套依赖版本,可有效规避 PyPI 新版本引入的兼容性问题。两方案的Pagure 本体均为 5.14.1,功能无差别。
七、配置说明(账号初始化与首次使用)
7.1 首个管理员账号与激活
按 4.9 配置(EMAIL_SEND = False)部署时,注册流程不发邮件,需要手动激活。流程实测如下。
浏览器或 curl 访问注册页 /user/new,提交表单(用户名 admin、密码 q1w2e3r4、邮箱 admin@pagure.lab)后,页面提示:
User created, please check your email to activate the account
由于邮件被关闭,直接从数据库取激活 token:
PGPASSWORD=q1w2e3r4 psql -h localhost -U pagure -d pagure -c 'SELECT "user", token FROM users;'
实测输出(注册后 token 非空,形如 40 位随机串):
user | token
-------+------------------------------------------
admin | TC9UXJSDNL9Q5Z99K499N23OTD1XQSVNY2GW5WB1
(1 row)
💡 说明:实测取到的 token 为 40 位字符串,上表为节选展示(实际 40 位完整显示)。用浏览器访问
http://localhost/confirm/<token>完成激活,页面提示Email confirmed, account activated。
激活后即可登录。若未激活就登录,会得到实测报错:
Invalid user, did you confirm the creation with the url provided by email?
⚠️ 注意:Pagure 的登录 POST 端点是
/dologin,注册页是/user/new,与常见框架习惯的/login、/register不同,自动化脚本对接时容易 404。激活接口为/confirm/<token>(不带尾部斜杠也有效)。
账号激活后,确认 4.9 配置里 PAGURE_ADMIN_USERS = ['admin'] 生效,即可用 admin 账号在 Web 界面创建项目(如本指南验证用的 public-demo),或使用管理 CLI(见《Pagure 5.14.1 常用 CLI 与运维命令详解》)。
7.2 常用配置调整
- 改对外访问地址:生产替换
APP_URL(如https://pagure.example.com/)、GIT_URL_SSH(如ssh://git@git.example.com/),改完systemctl restart pagure-web。 - 打开邮件通知:填好 SMTP 五项并把
EMAIL_SEND改 True,注册激活、PR 通知走邮件。 - 打开 WebHook:
WEBHOOK = True并保持 Redis 可用(Pagure 用 Redis 发事件流)。 - 完整参数(约 90 个键)的逐项语义与生产推荐值,见《Pagure 5.14.1 配置参数详解》。
八、常见问题(FAQ)
以下均为实测遇到的问题,按「现象 → 原因 → 解决」沉淀。
8.1 启动报 No module named '_gdbm'
现象:访问页面 500,Gunicorn 错误日志出现 ModuleNotFoundError: No module named '_gdbm'。
原因:binaryornot(文件类型识别依赖)运行时导入 Python 的 gdbm 模块,该模块属于 Python 标准库的扩展,只能以系统包形式安装,pip 装不上。
解决:apt install -y python3-gdbm,然后 systemctl restart pagure-web。无需重建虚拟环境——venv 与系统 Python 共享 lib-dynload 目录。
8.2 Python 依赖版本冲突报错汇总
Pagure 5.14.1 基于 SQLAlchemy 1.4 / Flask 2.2 时代的 API 开发,直接 pip install -r requirements.txt 会拉到过新的版本。实测踩坑与钉版对照:
| 报错(实测原文关键行) | 原因 | 钉版解决 |
|---|---|---|
ModuleNotFoundError: No module named 'pygit2.remote' | pygit2 1.14+ 重命名了远端模块 | pip install pygit2==1.13.3 |
cannot import name 'relation' from 'sqlalchemy.orm'(model.py:32) | SQLAlchemy 2.x 移除了 relation | pip install sqlalchemy==1.4.54 |
cannot import name 'Markup' from 'flask'(ui/plugins.py:19) | Flask 3.x 移除 Markup | pip install flask==2.2.5 werkzeug==2.3.8 |
no attribute 'Required'(forms.py:338) | wtforms 3.x 移除 Required | pip install wtforms==2.3.3 flask-wtf==1.2.1 |
No module named 'pkg_resources' | setuptools 81+ 移除 pkg_resources | pip install 'setuptools<81' |
TypeError: unsupported operand type(s) for +: 'frozenset' and 'list'(项目页 500) | bleach 6.x 收紧了 ALLOWED_TAGS 类型 | pip install bleach==5.0.1 |
8.3 SSH 推送报 pre-receive hook declined(No such file or directory 'python')
现象:git push 输出 remote: /usr/bin/env: 'python': No such file or directory 与 ! [remote rejected] main -> main (pre-receive hook declined)。
原因:Pagure 装到每个仓库的 pre-receive/post-receive 钩子是源码里的 hookrunner 脚本,其 shebang 为 #!/usr/bin/env python,而虚拟环境的解释器叫 python3,不在 gitolite 钩子执行环境的 PATH 里。
解决(Pagure 源码注释里自述的修法):把钩子解释器改为虚拟环境的绝对路径:
sed -i '1s|.*|#!/opt/pagure/venv/bin/python|' /opt/pagure/pagure/hooks/files/hookrunner
head -1 /opt/pagure/pagure/hooks/files/hookrunner
实测输出:
#!/opt/pagure/venv/bin/python
仓库里的钩子是对该文件的符号链接,改完无需重建仓库,重推即成功(实测输出见 4.13 第四层)。
8.4 worker 日志报 authorized_keys Permission denied
现象:journalctl -u pagure-worker 出现:
pagure.exceptions.PagureException: The command "HOME=/srv/git gitolite compile && HOME=/srv/git gitolite trigger POST_COMPILE" failed with
err:"b'FATAL: open /srv/git/.ssh/authorized_keys failed: Permission denied\n\n'"
原因:有人以 root 身份执行过 pagure-admin refresh-gitolite(或等价触发 gitolite 编译的操作),把 /srv/git/.ssh/authorized_keys 重写成 root 属主,之后 git 用户跑的 worker 编译任务写不了该文件。
解决:
chown git:git /srv/git/.ssh/authorized_keys
chmod 600 /srv/git/.ssh/authorized_keys
修复后验证 gitolite 编译与 SSH 通道均恢复正常:
su - git -c "HOME=/srv/git gitolite compile && echo COMPILE_OK"
实测输出:
COMPILE_OK
预防:所有会触发 gitolite 编译的管理命令一律以 git 用户(或对 /srv/git 有写权限的用户)执行。
8.5 gitolite info 看不到 Pagure 创建的仓库
现象:SSH 登录 git@localhost info 只显示 testing 等初始仓库,看不到 public-demo。
原因:Debian 版 gitolite3 把 GL_ADMIN_BASE(管理副本路径)默认改为 ~/.gitolite,与 Pagure 写入的 /srv/git/gitolite-admin 不一致,gitolite 编译时读的是另一份 conf。
解决:按 4.10 节在 /srv/git/.gitolite.rc 显式设置 GL_ADMIN_BASE => '/srv/git/gitolite-admin'(及 GL_REPO_BASE),再跑一次 pagure-admin refresh-gitolite。
8.6 以 root 跑 pagure-admin 部分子命令报仓库属主错误
现象:pagure-admin set-default-branch、create-branch 报:
_pygit2.GitError: /srv/git/repositories/public-demo.git: repository path '/srv/git/repositories/public-demo.git/' is not owned by current user
原因:这两个子命令直接操作 git 仓库(pygit2 有属主安全校验),root 身份触发保护。
解决:切到 git 用户执行:su - git -c "PAGURE_CONFIG=/etc/pagure/pagure.cfg /opt/pagure/venv/bin/pagure-admin set-default-branch public-demo main"(实测输出 Branch main set as default)。仅读写数据库的子命令(如 admin-token、list-groups)不受此限制。
8.7 直推报 Can't locate Gitolite/Hooks/Update.pm
现象:pagure-admin create-branch(内部直推 git 仓库)报 Can't locate Gitolite/Hooks/Update.pm in @INC,推送被 update 钩子拒绝。
原因:gitolite 的 update 钩子脚本第一段是 use lib $ENV{GL_LIBDIR},而 GL_LIBDIR 只在经 gitolite-shell 的 SSH 推送里由网关注入;Pagure 内部直推绕过了网关,环境变量为空导致 use lib 拿到空值。
解决:给 update 钩子加兜底路径(Debian 版 gitolite 库固定在 /usr/share/gitolite3/lib):
sed -i "s|use lib \$ENV{GL_LIBDIR};|use lib \$ENV{GL_LIBDIR} \|\| '/usr/share/gitolite3/lib';|" \
/srv/git/gitolite-admin/hooks/common/update
之后重跑 create-branch,实测输出 Branch created。经 gitolite-shell 的正常 SSH 推送不受影响(网关仍会注入 GL_LIBDIR)。
8.8 注册后登录报 Invalid user
现象:注册成功但登录提示 Invalid user, did you confirm the creation with the url provided by email?。
原因:账号未激活(数据库 users 表 token 字段非空即未激活)。EMAIL_SEND=False 时系统无法自动发激活邮件。
解决:按 7.1 从数据库取 token,访问 /confirm/<token> 激活。
九、卸载与清理
按「先停服务、再清数据」的顺序完整卸载:
systemctl disable pagure-web
systemctl disable pagure-worker
systemctl stop pagure-web
systemctl stop pagure-worker
rm -f /etc/systemd/system/pagure-web.service /etc/systemd/system/pagure-worker.service
systemctl daemon-reload
rm -f /etc/nginx/sites-enabled/pagure.conf
systemctl reload nginx
删除应用与数据(含 git 仓库,执行前确认已备份):
rm -rf /opt/pagure /etc/pagure /var/log/pagure /srv/git
su postgres -c "psql -c 'DROP DATABASE IF EXISTS pagure;'"
su postgres -c "psql -c 'DROP ROLE IF EXISTS pagure;'"
如不再使用,可一并卸载 gitolite3 与数据库软件包(保留则不影响其他服务):
apt purge -y gitolite3
卸载验证:
systemctl is-active pagure-web
实测输出:
inactive
(单元文件已删除时该命令输出 inactive 并提示单元不存在,属预期。)
十、参考链接
- Pagure 官方站点与主仓库:https://pagure.io/pagure
- 官方安装文档(源码内 doc/install.rst):https://pagure.io/pagure/blob/master/f/doc/install.rst
- 官方配置文档(源码内 doc/configuration.rst):https://pagure.io/pagure/blob/master/f/doc/configuration.rst
- GitHub 同步仓库(无 Releases,勿从此取版本):https://github.com/pagure/pagure
- 本文实测基线:Ubuntu 22.04.5 LTS + Pagure 5.14.1 + PostgreSQL 14.24 + Redis 6.0.16 + gitolite3 3.6.12-1(2026-10-28 实测)
- 延伸阅读:《Pagure 5.14.1 配置参数详解》、《Pagure 5.14.1 常用 CLI 与运维命令详解》、《Pagure 介绍与选型指南》