外观
Caddy 部署
适合谁
一台全新的服务器,想用最少的配置得到一个 HTTPS 网站的人。 Caddy 会自动申请和续期 Let's Encrypt 证书,整个 Web 服务器配置只有二十几行。
text
浏览器 ──HTTPS──► Caddy ──┬── /、/admin/ ─────► 静态网页(/opt/zebra/web)
└── /api/ 等 ───────► zebra-store(127.0.0.1:8081,systemd 守护)准备
| 项目 | 要求 |
|---|---|
| 服务器 | 64 位 Linux,下面以 Debian 12 / Ubuntu 22.04+ 为例,全程用 root |
| 域名 | shop.example.com 的 A 记录指向服务器 |
| 端口 | 80 和 443 没有被其他程序(例如 Nginx、Apache)占用,并在防火墙/安全组放行 |
| 文件 | zebra-store、storefront/dist、admin/dist、config.example.yml |
获取程序和网页
一共需要三样东西:
| 东西 | 是什么 | 怎么得到 |
|---|---|---|
zebra-store | 后端程序(一个文件) | 在 backend/ 编译 |
storefront/dist | 用户前台网页 | 在 storefront/ 打包 |
admin/dist | 管理后台网页 | 在 admin/ 打包,必须带 --base=/admin/ |
在你自己的电脑(装好 Rust ≥ 1.90 和 Node.js ≥ 20)上执行:
bash
git clone <仓库地址> zebra-store && cd zebra-store
# 1) 后端:给 Linux 服务器编译一个不依赖任何系统库的静态程序
cargo install cargo-zigbuild # 第一次需要;macOS 还要 brew install zig
cd backend
rustup target add x86_64-unknown-linux-musl
cargo zigbuild --release -p zs-server --target x86_64-unknown-linux-musl
# 产物:backend/target/x86_64-unknown-linux-musl/release/zebra-store
cd ..
# 2) 用户前台
cd storefront && npm ci && npm run build && cd .. # 产物:storefront/dist
# 3) 管理后台(放在 /admin/ 路径下)
cd admin && npm ci && npx vite build --base=/admin/ && cd .. # 产物:admin/dist直接在 Linux 服务器上编译
如果你就在 x86_64 Linux 服务器上编译,后端可以简单地用 cargo build --release -p zs-server,产物在 backend/target/release/zebra-store。 服务器是 ARM(arm64)的话,把上面的 x86_64-unknown-linux-musl 换成 aarch64-unknown-linux-musl。
为什么后台要加 --base=/admin/
后台网页默认假设自己放在网站根目录。我们把它放在 https://你的域名/admin/, 所以打包时要告诉它“我的根路径是 /admin/”,否则打开后台时 JS 文件会 404,页面一片空白。
步骤
1. 安装 Caddy
bash
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl sqlite3
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' > /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install -y caddy其他系统见 Caddy 官方安装文档。
2. 上传文件到固定目录
bash
mkdir -p /opt/zebra/{data,uploads,web}在你的电脑上:
bash
scp backend/target/x86_64-unknown-linux-musl/release/zebra-store root@服务器:/opt/zebra/
scp -r storefront/dist root@服务器:/opt/zebra/web/storefront
scp -r admin/dist root@服务器:/opt/zebra/web/admin
scp backend/config.example.yml root@服务器:/opt/zebra/config.yml写配置文件
从仓库复制 backend/config.example.yml,改名为 config.yml,至少改下面这些 (每一项的含义见 配置文件详解):
yaml
app:
secret_key: 第一串随机字符 # openssl rand -hex 24 生成
server:
host: 127.0.0.1 # 只让本机的反向代理访问后端
port: 8081
mode: release
jwt:
secret: 第二串随机字符
user_jwt:
secret: 第三串随机字符
bootstrap:
default_admin_username: admin
default_admin_password: 你的管理员密码 # 只在第一次启动时用来创建超级管理员
database:
url: sqlite://data/zebra.db?mode=rwc # 相对“运行目录”,即 <程序目录>/data/zebra.db
upload:
dir: uploads # 相对“运行目录”
cors:
allowed_origins: ["https://shop.example.com"]三个密钥
app.secret_key、jwt.secret、user_jwt.secret 必须是三个不同的值,每个至少 16 个字符, 不能保留示例里的 change-me-…,否则程序拒绝启动并在日志里写明是哪一项。 app.secret_key 用来加密数据库里的敏感字段(支付密钥等),上线后不要再改,改了之前加密的数据就解不开了。
3. 创建运行用户
bash
useradd --system --home /opt/zebra --shell /usr/sbin/nologin zebra
chown -R zebra:zebra /opt/zebra
chmod 600 /opt/zebra/config.yml
chmod +x /opt/zebra/zebra-store
chmod o+rx /opt/zebra /opt/zebra/web # 让 caddy 用户能读取网页文件4. 用 systemd 运行后端
创建 /etc/systemd/system/zebra-store.service:
ini
[Unit]
Description=Zebra Store
After=network-online.target
Wants=network-online.target
[Service]
User=zebra
Group=zebra
WorkingDirectory=/opt/zebra
ExecStart=/opt/zebra/zebra-store --config /opt/zebra/config.yml serve
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
ProtectSystem=full
ReadWritePaths=/opt/zebra
[Install]
WantedBy=multi-user.targetbash
systemctl daemon-reload
systemctl enable --now zebra-store
systemctl status zebra-store --no-pager
curl -s http://127.0.0.1:8081/api/v1/public/config | head -c 1205. 写 Caddyfile
哪些路径要转给后端
前台和后台只是静态网页,所有数据都来自后端。反向代理必须把下面这些路径转发给后端 127.0.0.1:8081, 并保留原始的 Host 头:
| 路径 | 用途 |
|---|---|
/api/ | 所有接口,包括支付回调和对接接口 |
/uploads/ | 上传的图片 |
/sitemap.xml、/robots.txt | 搜索引擎 |
/shared/ | 异次元发卡把本站当上游时调用(异次元对接) |
/plugin/open-api/ | 萌次元协议把本站当上游时调用(萌次元对接) |
其余路径:/admin/ 开头的返回后台网页,其他的返回前台网页;找不到文件时返回对应的 index.html (前台和后台都是单页应用,刷新 /products/xxx 这类地址时服务器上并没有这个文件)。
一定要保留 Host 头
后端靠 Host 判断访问的是主站还是哪个分站,生成支付回调地址时也会用到。
把 /etc/caddy/Caddyfile 的内容整个替换为:
text
shop.example.com {
encode zstd gzip
# 接口、图片、SEO 文件、兼容对接协议 → 后端
@backend path /api/* /uploads/* /sitemap.xml /robots.txt /shared/* /plugin/open-api/*
handle @backend {
reverse_proxy 127.0.0.1:8081
}
# 管理后台
redir /admin /admin/ 308
handle_path /admin/* {
root * /opt/zebra/web/admin
try_files {path} /index.html
file_server
}
# 用户前台
handle {
root * /opt/zebra/web/storefront
try_files {path} /index.html
file_server
}
header /assets/* Cache-Control "public, max-age=31536000, immutable"
}bash
caddy validate --config /etc/caddy/Caddyfile
systemctl reload caddy几秒钟后 Caddy 会自动拿到证书,https://shop.example.com 就能访问了。 journalctl -u caddy -f 可以看到申请证书的过程。
让后端拿到买家的真实 IP
风控、限流和登录日志都需要买家的真实 IP。反向代理通过 X-Forwarded-For 头把 IP 传给后端, 但后端只相信 server.trusted_proxies 里列出的代理。默认值是 127.0.0.1/32 和 ::1/128, 反向代理和后端在同一台机器上时不用改。
反向代理在别的机器或者 Docker 容器里时,把它的 IP 或网段加进去,例如:
yaml
server:
trusted_proxies: ["127.0.0.1/32", "::1/128", "172.28.0.0/24"]0.0.0.0/0 这种“相信所有人”的写法会被拒绝,因为那样任何人都能伪造 IP。
Caddy 和后端在同一台机器上,保持默认即可。Caddy 会自动带上 X-Forwarded-For 和原始 Host。
验证
逐项检查:
- [ ]
https://你的域名/能打开前台,标题是你的站点名; - [ ]
https://你的域名/admin/能打开后台登录页,用admin和配置里的密码能登录; - [ ]
https://你的域名/api/v1/public/config返回一段 JSON(开头是{"status_code":0); - [ ] 在
https://你的域名/products页面按 F5 刷新,不会出现 404; - [ ] 后台 内容管理 → 素材管理 上传一张图片,能正常显示(说明
/uploads/转发正常); - [ ] 浏览器地址栏有小锁,证书有效。
登录后台后,按 部署完成后要做的事 继续。
升级
bash
# 1. 备份(见下方)
# 2. 上传新文件到 /tmp,然后:
systemctl stop zebra-store
install -m 755 -o zebra -g zebra /tmp/zebra-store /opt/zebra/zebra-store
rm -rf /opt/zebra/web/storefront /opt/zebra/web/admin
mv /tmp/storefront-dist /opt/zebra/web/storefront
mv /tmp/admin-dist /opt/zebra/web/admin
systemctl start zebra-store新版本启动时自动补齐表结构,业务数据保留。网页换了之后 Caddy 不需要重启。
备份
要备份的只有三样:数据库、上传目录 uploads/、配置文件 config.yml(里面有加密密钥)。
bash
# SQLite 在线备份(不用停服务;需要 sqlite3:apt install sqlite3)
sqlite3 数据目录/zebra.db ".backup '/root/backup/zebra-$(date +%F).db'"
tar czf /root/backup/uploads-$(date +%F).tgz -C 程序目录 uploads
cp 程序目录/config.yml /root/backup/config-$(date +%F).ymlMySQL / PostgreSQL、定时备份和恢复步骤见 备份与升级。
这一页的“数据目录”是 /opt/zebra/data,“程序目录”是 /opt/zebra。
常见问题
证书一直申请不下来
- 域名是否已经解析到这台服务器(
ping shop.example.com看 IP); - Cloudflare 用户请把云朵设为灰色(仅 DNS),或者改用 Cloudflare 的源站证书;
- 80/443 是否被占用:
ss -ltnp | grep -E ':80 |:443 '; - 云服务商安全组是否放行 80/443。
permission denied 读取网页文件 执行 chmod o+rx /opt/zebra /opt/zebra/web,并确认 web/storefront/index.html 存在。
502 后端没运行,journalctl -u zebra-store -n 50 查看原因。
要开分站(子域名) 在第一行域名后面追加分站域名,例如 shop.example.com, sakura.example.com, neon.example.com {, 每个子域名都要先加 DNS 解析。详见 分站。