Shhh 3.2.4 Ubuntu 22.04 源码部署指南

目标:在 Ubuntu 22.04 服务器上以源码方式部署 Shhh 3.2.4:Python 3.12 虚拟环境、PostgreSQL 14、Alembic 迁移、Gunicorn 加 systemd 常驻,并完成创建/读取/一次性销毁全链路验证;全部命令与回显为实测记录。

ShhhShhh2026/10/27

Shhh 3.2.4 Ubuntu 22.04 源码部署指南

目标:在一台 Ubuntu 22.04 服务器上以源码方式部署 Shhh 3.2.4(Git 检出官方 3.2.4 tag → Python 3.12 虚拟环境 → PostgreSQL 14 → 数据库迁移 → Gunicorn 运行 → systemd 常驻),并完成创建/读取/一次性销毁的全链路功能验证。全部命令与回显均为 2026-10-26/27 在实测服务器上逐条执行的真实记录;容器化部署路线见姊妹篇《Shhh 3.2.4 Docker Compose 部署指南》。

一、环境信息

项目值
服务器操作系统Ubuntu 22.04.5 LTS(主机名 shhh-server)
内核7.0.0-38-generic
Shhh 版本3.2.4(Git tag 3.2.4,commit ab04913,官方 2025-10-22 发布)
Python3.12.15(deadsnakes PPA,系统自带为 3.10)
数据库PostgreSQL 14.24(本机 apt 安装)
部署目录 / 运行端口/opt/shhh / 8081
实测日期2026-10-26 ~ 2026-10-27

⚠️ 注意:Ubuntu 22.04 仓库自带的 Python 是 3.10,而 Shhh 3.2.4 按官方声明需要 Python 3.12(官方 README 要求 "a version of Python 3.12")。本文通过 deadsnakes PPA 安装 3.12,与系统 Python 3.10 共存互不影响。

二、部署思路与组件链

Shhh 是一个 Flask 单体应用,源码部署不涉及编译产物,整条链路为:

apt 系统依赖(git / Python 3.12 / PostgreSQL 14)
  → git 检出 3.2.4 tag 到 /opt/shhh
  → python3.12 -m venv 隔离依赖
  → pip 安装 requirements.txt(Flask 3 / gunicorn / psycopg2-binary 等)
  → PostgreSQL 建用户与数据库
  → flask db upgrade 建表(Alembic 迁移,必做)
  → gunicorn 监听 8081(与官方运行参数一致:3 worker + --preload)
  → systemd 托管常驻、开机自启

两个关键设计决策:

  • 专用系统用户运行:服务以无登录 shell 的 shhh 用户运行,环境变量文件收权到 root:shhh 640,避免数据库口令散落在全局可读位置。
  • 迁移前置:Shhh 的数据库表结构由 Alembic 管理,程序启动不会自动建表,flask db upgrade 是必须显式执行的一步(见第八节)。

三、安装系统依赖

以下命令在本机已装有上述包的服务器上复核执行(回显为幂等重跑的真实输出;全新服务器上会看到对应的下载安装过程):

apt-get update
apt-get install -y git python3.12 python3.12-venv python3.12-dev postgresql postgresql-contrib
python3.12-venv is already the newest version (3.12.15-1+jammy1).
0 upgraded, 0 newly installed, 0 to remove and 2 not upgraded.

Python 3.12 来自 deadsnakes PPA(实测服务器 /etc/apt/sources.list.d/ 下为 deadsnakes-ubuntu-ppa-jammy.list)。若你的服务器尚未添加该 PPA:

apt-get install -y software-properties-common
add-apt-repository -y ppa:deadsnakes/ppa
apt-get update

添加后 apt policy python3.12 确认可装版本(实测输出):

python3.12:
  Installed: 3.12.15-1+jammy1
  Candidate: 3.12.15-1+jammy1
  Version table:

四、安装并初始化 PostgreSQL

Ubuntu 仓库的 PostgreSQL 14 随上一步装好后确认服务状态(实测):

systemctl status postgresql --no-pager | head -5
● postgresql.service - PostgreSQL RDBMS
     Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; vendor preset: enabled)
     Active: active (exited) since Mon 2026-10-26 17:23:31 UTC; 6h ago

创建 Shhh 专用数据库用户与数据库(口令换成你自己的强口令;本文统一以 q1w2e3r4 作示例):

runuser -u postgres -- psql -c "CREATE USER shhh WITH PASSWORD 'q1w2e3r4';"
runuser -u postgres -- psql -c "CREATE DATABASE shhh OWNER shhh;"
CREATE ROLE
CREATE DATABASE

本部署走本机 TCP 连接(DB_HOST=127.0.0.1),PostgreSQL 14 默认 pg_hba.conf 对本机回环的口令认证(scram-sha-256)无需改动。

五、获取源码

检出官方 3.2.4 tag(注意 tag 名无 v 前缀,git clone --branch v3.2.4 会报 Remote branch v3.2.4 not found):

git clone --branch 3.2.4 --depth 1 https://github.com/smallwat3r/shhh.git /opt/shhh
Cloning into '/opt/shhh'...
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

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

⚠️ 实测时直连 github.com 偶发 Failed to connect ... Connection refused,等待片刻或在 shell 中配置标准 HTTP 代理环境变量(export http_proxy=... https_proxy=...)后重跑即可;仓库很小(约 1MB),克隆本身几秒完成。非交互 shell 下 git 不打印进度百分比,上述回显即完整输出。

确认版本(实测):

cd /opt/shhh && git log --oneline -1
ab04913 Increase test coverage

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

python3.12 -m venv /opt/shhh/.venv
/opt/shhh/.venv/bin/pip install -r /opt/shhh/requirements.txt

pip 结束段(实测,截取末尾):

[notice] A new release of pip is available: 25.0.1 -> 26.2.2
[notice] To update, run: python3.12 -m pip install --upgrade pip

关键依赖实测版本(.venv/bin/pip list 截取):

包实测版本
Flask3.1.3
Flask-Alembic3.3.0
Flask-APScheduler1.13.1
Flask-SQLAlchemy3.1.1
gunicorn26.2.0
alembic1.20.1
cryptography50.0.2
psycopg2-binary2.9.13

psycopg2 走的是 psycopg2-binary wheel 包,无需额外安装 libpq-dev 编译头文件。

七、配置环境变量文件

Shhh 全部配置来自环境变量。写入 /opt/shhh/shhh.env(systemd 的 EnvironmentFile 将加载它):

cat > /opt/shhh/shhh.env <<'EOF'
FLASK_ENV=production
FLASK_APP=wsgi.py
DB_HOST=127.0.0.1
DB_PORT=5432
DB_USER=shhh
DB_PASSWORD=q1w2e3r4
DB_NAME=shhh
DB_ENGINE=postgresql+psycopg2
EOF

各变量含义与可选变量(SHHH_HOST、SHHH_SECRET_MAX_LENGTH 等)逐项说明见姊妹篇《Shhh 3.2.4 配置参数详解》。此处注意两点:

  • FLASK_ENV 必填,合法值 testing / dev-local / dev-docker / heroku / production,生产固定 production;缺省它时程序直接拒绝启动(报 RuntimeError: env=None specified in FLASK_ENV is not supported)。
  • DB_ENGINE 是 SQLAlchemy 连接串前缀,PostgreSQL 固定 postgresql+psycopg2。

收紧权限(数据库口令明文落盘,不能全局可读):

chown root:shhh /opt/shhh/shhh.env && chmod 640 /opt/shhh/shhh.env

八、执行数据库迁移

这一步不可跳过——Shhh 启动后不会自动建表,未迁移时创建秘密会报错。迁移命令需临时带上环境变量(即第七节 env 文件中的全部 DB_* 与 FLASK_*):

cd /opt/shhh
set -a; source /opt/shhh/shhh.env; set +a
.venv/bin/flask db upgrade

实测回显(Alembic 执行了两个 revision):

[Tue, 27 Oct 2026 00:09:12] [sev 20] [INFO] [alembic.runtime.migration]> Context impl PostgresqlImpl.
[Tue, 27 Oct 2026 00:09:12] [sev 20] [INFO] [alembic.runtime.migration]> Will assume transactional DDL.
[Tue, 27 Oct 2026 00:09:12] [sev 20] [INFO] [alembic.runtime.migration]> Running upgrade  -> 1730636773, initial
[Tue, 27 Oct 2026 00:09:12] [sev 20] [INFO] [alembic.runtime.migration]> Running upgrade 1730636773 -> 1730637997, add indexes

看到 Running upgrade -> 1730636773, initial 与 -> 1730637997, add indexes 即代表 secret 等表已建好。以后升级 Shhh 版本后,重复执行 flask db upgrade 即可增量迁移。

九、创建专用用户与 systemd 服务

创建无登录 shell 的运行用户并移交目录属主:

useradd -r -s /usr/sbin/nologin shhh
chown -R shhh:shhh /opt/shhh

写入服务单元 /etc/systemd/system/shhh.service(gunicorn 参数与官方运行方式一致:绑定 8081、3 个 worker、--preload):

cat > /etc/systemd/system/shhh.service <<'EOF'
[Unit]
Description=Shhh secret sharing service
After=network.target postgresql.service
Wants=postgresql.service

[Service]
Type=simple
User=shhh
Group=shhh
WorkingDirectory=/opt/shhh
EnvironmentFile=/opt/shhh/shhh.env
Environment=HOME=/opt/shhh
ExecStart=/opt/shhh/.venv/bin/gunicorn -b 0.0.0.0:8081 -w 3 wsgi:app --preload
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

💡 其中 Environment=HOME=/opt/shhh 不是装饰——gunicorn 26 的控制服务(control server)默认在运行用户家目录下建控制套接字,而 useradd -r 创建的系统用户没有家目录,会打出权限报错(详见第十二节排障实录)。把 HOME 指到部署目录即可。

十、启动与自检

systemctl daemon-reload
systemctl enable --now shhh

enable 实测回显:

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

状态自检(实测,节选):

systemctl status shhh --no-pager | head -10
● shhh.service - Shhh secret sharing service
     Loaded: loaded (/etc/systemd/system/shhh.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2026-10-27 00:09:37 UTC; 2s ago
   Main PID: 7842 (gunicorn)
      Tasks: 5 (limit: 2932)
     Memory: 66.9M
        CPU: 371ms
     CGroup: /system.slice/shhh.service
             ├─7842 /opt/shhh/.venv/bin/python3.12 /opt/shhh/.venv/bin/gunicorn -b 0.0.0.0:8081 -w 3 wsgi:app --preload
             ├─7845 /opt/shhh/.venv/bin/python3.12 /opt/shhh/.venv/bin/gunicorn -b 0.0.0.0:8081 -w 3 wsgi:app --preload

active (running)、常驻内存约 67MB——单实例 Shhh 的资源占用非常轻。页面自检:

curl -sS -I http://127.0.0.1:8081/ | head -4
HTTP/1.1 200 OK
Server: gunicorn
Date: Tue, 27 Oct 2026 00:11:29 GMT
Connection: close

若服务器启用了 UFW 防火墙,放行端口后再对外提供服务:ufw allow 8081/tcp。

十一、功能验证(创建 → 读取 → 一次性销毁)

用 REST API 走通完整语义(API 参数与错误码全集见姊妹篇《Shhh 3.2.4 API 使用详解》)。口令需满足强度规则:至少 8 字符、含 1 个数字、1 个大写字母,示例用 Q1w2e3r4。

1. 创建一条秘密(有效期 1 天、最多尝试 5 次):

curl -sS -w '\nHTTP_STATUS:%{http_code}\n' -X POST http://127.0.0.1:8081/api/secret \
  -H 'Content-Type: application/json' \
  -d '{"passphrase":"Q1w2e3r4","secret":"ubuntu-src-deploy-check-9k2mTz","expire":"1d","tries":5}'
{"response":{"details":"Secret successfully created.","expires_on":"October 28, 2026 at 00:10 UTC","external_id":"1Bs2SeAGy9Y6v0X3vchj","link":"http://127.0.0.1:8081/secret/1Bs2SeAGy9Y6v0X3vchj","status":"created"}}

HTTP_STATUS:201

2. 用正确口令读取一次(返回原文,记录随即删除):

curl -sS -w '\nHTTP_STATUS:%{http_code}\n' \
  'http://127.0.0.1:8081/api/secret?external_id=1Bs2SeAGy9Y6v0X3vchj&passphrase=Q1w2e3r4'
{"response":{"msg":"ubuntu-src-deploy-check-9k2mTz","status":"success"}}

HTTP_STATUS:200

3. 再次读取同一链接(一次性语义生效,永久失效):

{"response":{"msg":"Sorry, we can't find a secret, it has expired, been deleted or has already been read.","status":"expired"}}

HTTP_STATUS:404

三步全部符合预期,部署完成。浏览器访问 http://<服务器IP>:8081 即是日常使用的创建页面。

十二、排障实录

12.1 日志出现 Control server error: Permission denied: '/home/shhh'

现象(journalctl -u shhh 实测原文):

Oct 27 00:09:37 shhh-server gunicorn[7842]: [2026-10-27 00:09:37 +0000] [7842] [ERROR] Control server error: [Errno 13] Permission denied: '/home/shhh'

定位:gunicorn 26 新增的控制服务默认在运行用户家目录创建控制套接字;第九节用 useradd -r 创建的系统用户没有家目录,gunicorn 尝试访问 /home/shhh 被拒。该报错不影响主服务(Web 端口正常、业务功能完整),但日志里带着 ERROR 不干净。

修复(实测有效):给服务补一个可写的 HOME,指向部署目录。在 /etc/systemd/system/shhh.service 的 [Service] 段加:

Environment=HOME=/opt/shhh

然后 systemctl daemon-reload && systemctl restart shhh,再查日志(实测):

Oct 27 00:11:35 shhh-server gunicorn[7914]: [2026-10-27 00:11:35 +0000] [7914] [INFO] Control socket listening at /opt/shhh/.gunicorn/gunicorn.ctl

ERROR 消失,控制套接字正常落在 /opt/shhh/.gunicorn/。

12.2 git clone --branch v3.2.4 报分支不存在

现象:fatal: Remote branch v3.2.4 not found in upstream origin。Shhh 的发布 tag 不带 v 前缀,正确写法是 --branch 3.2.4(本文第五节命令)。

12.3 迁移命令报 RuntimeError: env=None specified in FLASK_ENV is not supported

FLASK_ENV 未设置时,Shhh 的配置加载器直接抛此异常。用第七节的 env 文件 source 后再执行迁移即可;systemd 形态下由 EnvironmentFile 兜底,服务启动不受影响。

十三、日常运维

操作命令
查看服务日志journalctl -u shhh -f
重启 / 停止systemctl restart shhh / systemctl stop shhh
手动跑迁移set -a; source /opt/shhh/shhh.env; set +a; cd /opt/shhh && .venv/bin/flask db upgrade
升级版本cd /opt/shhh && git fetch --tags && git checkout <新tag> && .venv/bin/pip install -r requirements.txt && flask db upgrade && systemctl restart shhh
数据备份PostgreSQL 层面:runuser -u postgres -- pg_dump shhh > shhh-$(date +%F).sql

数据库是 Shhh 唯一的状态载体(秘密密文、过期时间、剩余尝试次数都在其中),备份与迁移围绕 PostgreSQL 做即可;应用目录本身无状态,重建无风险。

十四、参考链接