跳到主要内容

SilverBullet 私人笔记部署:分清新版空间、访问权限与完整备份

固定新版多空间部署,核对私人笔记的资源、权限与 Primary URL,完整保存账号、空间配置和文件并从副本恢复。

先确认笔记保存在哪里

笔记已经分成 Markdown 文件,换一台电脑时却找不到附件,或者不知道哪一份修改已保存到服务器。SilverBullet 可以把笔记、链接和附件放进浏览器里的同一空间。先用一个不含敏感资料的小空间验证保存和恢复,再考虑迁移旧笔记。

本文固定 SilverBullet 2.12.1-slim,采用新版默认的多空间模式,不安装外部扩展,也不调用浏览器 Runtime API。这个版本的初始账号流程与旧 SB_USER 单空间教程不同,数据目录和启动参数都要一起核对。

先按附件与访问位置选机器

已有 Linux VPS 还有内存和磁盘余量,可以先复用。笔记多为文字时,附件、历史备份和恢复副本往往比正文占用更多空间;先统计现有文件,再为备份与一次恢复留出额外空间。不要只看压缩后的笔记包大小。

示例给应用设 512 MiB 内存上限,这是部署配置,不是本站测出的最低要求。宿主机、反向代理和备份过程另需余量;索引较大的空间或添加扩展后,应重新观察内存与响应。slim 镜像不包含 Chromium,不能据此使用依赖浏览器的 Runtime API。

机房优先考虑自己经常写笔记的设备能否稳定访问。首次加载和附件上传需要服务器连接,套餐端口速率不能代替实际使用验证。确实需要新实例时,比较雨云 RCS 的地区、本地磁盘、月付与续费,把域名和异地备份也计入费用。私人笔记不需要住宅 IP,先完成短周期验证再决定长账期。

查看雨云 RCS 的地区与配置 ↗

注册推荐码:KuZhuJi

首次启动要用真正的新空目录

以下在已有 Docker Engine、Compose 插件与 Python 3 的 Linux root shell 中执行。为数据建立专用系统用户,镜像启动后按该 UID/GID 运行。若已有同名账号或部署,先检查原归属和数据,不重复执行初始化。

sh;使用前请核对本文前提。
set -eu
umask 077
test ! -e /srv/silverbullet
test ! -L /srv/silverbullet
if getent passwd silverbullet >/dev/null; then
  printf '同名系统账号已存在,先核对原部署\n' >&2
  exit 1
fi
useradd --system --user-group --home-dir /nonexistent \
  --shell /usr/sbin/nologin silverbullet
install -d -m 700 /srv/silverbullet
install -d -m 700 -o silverbullet -g silverbullet /srv/silverbullet/data
cd /srv/silverbullet
test ! -e compose.yaml
test ! -e .env
test -z "$(find data -mindepth 1 -maxdepth 1 -print -quit)"
printf 'SB_UID=%s\nSB_GID=%s\n' \
  "$(id -u silverbullet)" "$(id -g silverbullet)" > .env
chmod 600 .env

compose.yaml 和 .env 放在 data 外面。不要先向 data 放一份 index.md,也不要照搬 SB_USER、SB_AUTH_TOKEN、SB_NAME 或 SB_URL_PREFIX 等旧单空间环境变量。2.12.1 会据旧参数或已有普通文件选择兼容模式,不能再假定一定出现新版管理员设置向导。

保存 compose.yaml,固定版本和多架构索引摘要。端口只绑定服务器回环地址。

yaml;使用前请核对本文前提。
services:
  silverbullet:
    image: ghcr.io/silverbulletmd/silverbullet:2.12.1-slim@sha256:7e24c16d31a3f4b4a4832ed64ec585e605a1e275b7c35917d385176f0b40415c
    restart: unless-stopped
    environment:
      PUID: ${SB_UID:?dedicated UID required}
      PGID: ${SB_GID:?dedicated GID required}
    ports:
      - "127.0.0.1:3000:3000"
    volumes:
      - ./data:/data
    mem_limit: 512m
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
sh;使用前请核对本文前提。
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=80 silverbullet

数据根目录中的 CONTAINER_BOOT.md 会被启动脚本当作程序执行,而且发生在应用降权之前。本例不创建它。只恢复自己信任的归档,不把下载来的整个“示例笔记库”直接当作服务数据根启动;设置非 root 的 PUID 也不能消除这个启动脚本风险。

管理员与空间权限在私人入口设置

在自己的电脑另开终端,替换 SSH 用户和主机:

sh;使用前请核对本文前提。
ssh -N -L 13001:127.0.0.1:3000 user@your-server

打开 http://127.0.0.1:13001,按初始向导创建管理员并设置独立密码,再进入 Dashboard 创建自己的空间,保留默认 data/spaces 内的目录与默认路径绑定,不选外部绝对路径、额外挂载或专用域名绑定。若直接看到一个无需登录的旧式空间,先检查 data 内容和旧环境变量,不在里面放真实笔记后再补认证。

私人空间的匿名访问设为 none,只给需要的账号分配 read 或 write。管理员能够访问所有空间,不能拿另一个管理员账号验证成员隔离。使用普通成员和未登录窗口分别检查正文、附件与 API 的访问结果,只有验证过的入口才安排外部访问。

write 权限还涉及空间内可执行的 Space Lua。共享给别人编辑时,要把这视为较高信任权限,不能当作只能修改几个段落的受限角色。本文不启用 shell、不导入外部脚本或扩展,也不连接模型服务;以后增加这些能力时,重新检查可访问的数据和网络出口。

宿主机已有 Caddy 时,可加入以下命名片段,再在自己的真实域名站点块内添加 import silverbullet_backend。DNS、80/443 和证书申请条件需满足,验证配置后再重载,保留已有站点。

caddyfile;使用前请核对本文前提。
(silverbullet_backend) {
    reverse_proxy 127.0.0.1:3000
}

初次通过 SSH 设置时,Dashboard 的 Primary URL 会采用当时的浏览器地址。完成 DNS 与 HTTPS 代理、确认新域名入口可达后,保留原 SSH 入口,在原地址的 Dashboard 服务器设置中把 Primary URL 改成自己的实际 HTTPS origin,再从新域名登录;同时核对空间 Address 中的 host 和路径。服务器会检查 Dashboard 的 origin,不能只换代理地址而保留旧回环 URL,否则可能跳回旧地址或拒绝写入。

正式使用采用 HTTPS。远端普通 HTTP 与本机回环测试的浏览器条件不同,不能因为页面能打开,就认定离线功能、安装式网页应用和同步都能正常工作。代理运行在别的容器时,127.0.0.1 指向代理自身,需按已有网络连接应用。

先检查一篇笔记和一个附件

在空间里写一篇测试笔记,链接到另一篇笔记,再附上一张无个人信息的小图片。保存后离开页面、重新打开,核对标题、链接和附件;再从独立浏览器配置登录,确认服务器上的修改确实可见。浏览器自己还留着文字,不能证明另一端已经收到。

新版本的数据根包含账号、空间与服务配置,实际空间文件通常在 spaces 下的独立目录中。先在服务器核对自己的空间路径和 Markdown 文件,再考虑外部编辑器;不要把整份 data 当成单个笔记库同步到客户端,里面还有不应公开的账号与配置资料。

需要离线编辑时,先用测试笔记断网修改、重新联网,再从另一端核对结果。换设备、清除站点数据或恢复旧副本之前,先保存尚未同步的内容。只看到同名笔记,不足以说明两端内容一致;不要让浏览器本地缓存成为唯一副本。

账号配置与笔记一起冷备

多空间模式下,只复制某个空间的 Markdown 文件,会漏掉账号、空间权限与服务配置。本文只使用 data 内的默认空间目录,保存整个 data、Compose 和 .env;已有空间指向外部目录时,那些文件需要另外纳入同一轮备份。先让所有编辑端完成保存并暂停修改;未上传的客户端内容不在服务器备份里。

在维护窗口执行以下小空间冷备。脚本在停止失败、OOM 或归档异常时拒绝把半成品当作完成备份,并尝试启动原服务。

sh;使用前请核对本文前提。
set -eu
cd /srv/silverbullet
umask 077
mkdir -p /srv/silverbullet-backups
snapshot=$(mktemp -d /srv/silverbullet-backups/cold-XXXXXX)
cid=$(docker compose ps -q silverbullet)
test -n "$cid"
trap 'docker compose start silverbullet >/dev/null' EXIT
docker compose stop -t 60 silverbullet
state=$(docker inspect --format '{{.State.Status}} {{.State.OOMKilled}} {{.State.ExitCode}}' "$cid")
case "$state" in
  "exited false 0"|"exited false 143") ;;
  *) printf '未确认正常停止,拒绝归档:%s\n' "$state" >&2; exit 1 ;;
esac
tar --numeric-owner -czpf "$snapshot/instance.tar.gz.partial" \
  -C /srv/silverbullet compose.yaml .env data
tar -tzf "$snapshot/instance.tar.gz.partial" >/dev/null
mv "$snapshot/instance.tar.gz.partial" "$snapshot/instance.tar.gz"
(cd "$snapshot" && sha256sum instance.tar.gz > SHA256SUMS)
docker compose start silverbullet
trap - EXIT
printf '备份目录:%s\n' "$snapshot"

归档包含私人笔记、附件、账号和空间配置,应加密保存到另一台设备。压缩包和校验文件都留在同一块服务器磁盘上,无法应对整机丢失。

在有额外资源的 Linux 测试主机从新空目录恢复,只解压自己创建的可信包。将 snapshot 替换为实际目录:

sh;使用前请核对本文前提。
set -eu
snapshot=/srv/silverbullet-backups/cold-实际后缀
(cd "$snapshot" && sha256sum -c SHA256SUMS)
restore=$(mktemp -d /srv/silverbullet-restore.XXXXXX)
tar --numeric-owner -xzpf "$snapshot/instance.tar.gz" -C "$restore"
cd "$restore"
test ! -e data/CONTAINER_BOOT.md
cp compose.yaml compose.restore.yaml

恢复包也带着原来的 Primary URL。只在当前恢复副本中修改 data/server.json,将它设为接下来从自己电脑访问的 http://127.0.0.1:13002,保留其他字段。下面检查当前目录名,避免把命令贴到正式目录:

sh;使用前请核对本文前提。
python3 - <<'PYRESTORE'
import json
from pathlib import Path
root = Path.cwd().resolve()
if not root.name.startswith("silverbullet-restore."):
    raise SystemExit("仅在本次新恢复目录修改")
p = root / "data/server.json"
d = json.loads(p.read_text())
if "primaryUrl" not in d:
    raise SystemExit("缺少预期字段,先核对固定版本配置")
d["primaryUrl"] = "http://127.0.0.1:13002"
p.write_text(json.dumps(d, ensure_ascii=False, indent=2) + "\n")
PYRESTORE

本例空间保留默认路径绑定;已有空间显式绑定正式 host 时,还需在副本中核对并调整绑定,不能修改正式 spaces 配置或伪造代理头跳过 origin 校验。

检查 .env 中 UID/GID 与测试主机的数据目录实际归属。迁往另一主机时,不能假定相同数字仍属于同一账号;先核准专用用户,再同时调整副本目录归属和 .env,不操作正式 data。

在 compose.restore.yaml 中,只把副本的回环端口改成 127.0.0.1:13000:3000,restart 改成 "no",其余镜像、PUID/PGID 与 ./data 挂载保持一致。本文配置不启用外部集成;有脚本、通知或扩展的旧空间,先停用这些能力,再启动副本。

sh;使用前请核对本文前提。
project="silverbullet-recovery-$(basename "$restore" | tr 'A-Z.' 'a-z-')"
docker compose -p "$project" -f compose.restore.yaml config --quiet
docker compose -p "$project" -f compose.restore.yaml up -d
docker compose -p "$project" -f compose.restore.yaml logs --tail=80 silverbullet

在自己的电脑另开 SSH 转发,将 user@test-server 替换为测试主机:

sh;使用前请核对本文前提。
ssh -N -L 13002:127.0.0.1:13000 user@test-server

用新的浏览器配置打开 http://127.0.0.1:13002。回环绑定限制入站范围,并不禁止容器向外联网,不能把它称为断网隔离。需要完全禁止出口时,另做网络限制再启动。

核对原账号、空间匿名权限、测试笔记与附件字节,再写入一条新测试笔记并重启副本,确认仍能找回。核对通过前保持恢复待验,不以首页或校验值代替完整恢复。验完在副本目录执行 docker compose -p "$project" -f compose.restore.yaml stop,保留数据和记录。

升级与扩展分开处理

升级前阅读目标版本说明,先用原镜像恢复一次,保存完整旧 data、配置和镜像标识,再更改正式版本与摘要。升级后重新核账号、空间、附件和跨设备保存;更新过的浏览器客户端也要重新加载并检查版本。

新版迁移数据后,旧镜像未必能读取新目录。需要退回时,用升级前的整套数据与旧镜像配套验证。不要在同一次操作里同时升级应用、导入陌生空间、启用 shell 和更换机房,否则出了问题很难确定来源。

本文关联资源