首页/文档

ImageHub 完整文档

从安装部署到存储对账,把「自己能完全掌控」这件事讲透。按顺序读一遍即可独立完成一台实例的搭建与长期运维。

Next.js 15 · React 19 Prisma 6 · PostgreSQL / SQLite GPL-3.0 开源 单实例 自托管
目录
  1. 项目概览
  2. 核心特性
  3. 技术架构
  4. 系统要求
  5. 安装部署
  6. 首次使用
  7. 环境变量
  8. 存储与多桶
  9. 存储对账
  10. 邮件与通知
  11. 权限模型
  12. 上传机制
  13. 画廊与相册
  14. 动态与日常记录
  15. 备份与还原
  16. 运维命令
  17. 反向代理
  18. 安全说明
  19. 常见问题
  20. 目录结构

01项目概览

ImageHub 是一个单实例、自托管的图床与画廊应用:把图片与视频放在你自己的服务器上, 通过浏览器管理,不依赖任何第三方云服务。全站需要登录,文件二进制只经过站内受保护路由输出, 公开链、私有下载、缩略图缓存都有独立策略。

它面向的是这样一群人:有一台能跑 Docker 的机器(家用小主机、NAS、云服务器都行), 想要一个「数据在自己手里、不用付费订阅、不必担心服务关停」的相册与图床, 同时又不愿意牺牲现代 Web 应用的使用体验。

设计取舍:不做社交、不做多租户 SaaS,只把「隐私、可控、运维体验」三件事做扎实。因此没有花哨的推荐流,也没有把数据托管到别人服务器上的可能。

02核心特性

私有优先

全站登录才可见;公开、友链、私有三级可见性,随时可切换,私有图片始终走后端鉴权。

多桶存储

本地磁盘 + 任意数量 S3 兼容桶(R2 / OSS / COS / MinIO),每张图记录真实位置,可任意互迁。

大文件上传

超过 16MB 自动分片,断点续传只补缺片;sha256 秒传去重,内存占用恒为单片大小。

画廊与相册

瀑布流画廊、灯箱原图、相册封面;缩略图三级缓存,大列表滚动不卡顿。

筛选与整理

按存储位置、画廊状态、相册归属组合筛选;筛选同样作用于全选,批量操作不越界。

灯箱旋转

自己的图片可在灯箱内左右旋转 90°;服务端原位重写并刷新缓存,方向立刻更正。

图文动态

朋友圈式发布,配九宫格图片;照片自动汇总进「日常记录」,带上传者与时间标记。

两级后台

超级管理员管用户、设置、迁移与备份;管理员只做内容审核,看不到他人私密内容。

存储对账

双向核对数据库与桶内对象:缺文件的记录可清理,桶内残留可收录或删除,附图片预览。

备份还原

后台一键整包备份(数据库 + 存储),管理脚本可装每日 04:00 自动备份。

03技术架构

整套系统由两个容器组成:一个 Web 容器负责页面、接口与缩略图,一个 PostgreSQL 容器负责元数据。文件默认落在 Docker 卷里,也可以外接对象存储桶。

层次技术选型说明
前端Next.js 15 App Router + React 19 + Tailwind CSS 4服务端渲染页面与客户端交互组件混合
鉴权Auth.js v5(NextAuth)JWT 会话 + tokenVersion 版本号,可主动吊销
数据层Prisma 6 + PostgreSQL 16生产用 PostgreSQL,本地开发用 SQLite,模型一致
存储层本地磁盘 / AWS SDK S3 客户端统一驱动接口,本地与远端桶可混用
图像处理sharp四档缩略图(400/800/1600/2560)+ AVIF/WebP 自适应,可选自动转 WebP
限流ioredis(可选)/ 进程内存配置 Redis 时计数落 Redis,否则单实例内存降级
邮件nodemailer后台配置 SMTP,带连接状态检测;未配置时打印到控制台

一次图片请求的路径

  1. 浏览器请求站内图片地址(如 /api/images/:id/file)。
  2. 中间件校验会话与路由可见性,未登录直接跳转登录页。
  3. 服务层查库确认图片存在、归属与可见性,再做权限判断。
  4. 按记录里的存储位置(本地卷或某个桶)读取二进制,流式写回响应。
  5. 缩略图按显示宽度取档(400 / 800 / 1600 / 2560):现代浏览器优先返回 AVIF,其余回退 WebP,并带 ETag 支持 304;命中缓存直接返回,未命中时生成后落盘复用,同时生成的张数有限流,避免把小机器的 CPU 打满。

图片二进制不直接暴露为静态目录。Nginx 里不要给 /storage 做 alias,否则私有图片的鉴权会被绕过。

04系统要求

项目最低建议
操作系统Linux(x86_64 / arm64)、macOSDebian / Ubuntu LTS
内存1 GB2 GB 以上(大图批量上传更稳)
磁盘按图库规模自定预留 2 倍于预期图片体积
Docker20.10+最新稳定版 + Compose v2
数据库PostgreSQL 16由 compose 自带,无需单独安装
Node.js20+(仅本地开发需要)22 LTS

默认只监听 127.0.0.1,也就是只有本机能打开。这既是安全默认,也意味着你需要一台能通过 SSH 或内网访问的机器。

05安装部署

镜像由 GitHub Actions 构建并发布到 GHCR,服务器上不需要本地构建,也不需要装 Node.js。

方式一:一键安装(推荐)

curl -fsSL https://raw.githubusercontent.com/YsSZ-INK/ImageHub/main/install.sh | bash

脚本会检查并安装 Docker、拉取镜像、生成 .env(自动生成 AUTH_SECRET)、启动服务,最后引导你注册站长账号。交互问答约 6 项,全程可以回车走默认值。

方式二:Docker Compose

git clone https://github.com/YsSZ-INK/ImageHub.git
cd ImageHub
cp .env.example .env      # 修改 SITE_URL 与 AUTH_SECRET
docker compose pull web
docker compose up -d
curl http://localhost:3000/api/health   # {"ok":true,"db":"up"}

AUTH_SECRET 必须自己生成:openssl rand -base64 32。缺失、为默认值或长度不足时应用会直接启动失败 —— 这是刻意设计,避免人人可伪造会话。

方式三:本地开发

npm install
npm run db:push     # SQLite 建表(prisma/dev.db)
npm run dev         # http://localhost:3000

next dev 按需编译,首次打开某个页面会慢几秒,属于正常现象;要评估真实性能请用 npm run build && npm start。

数据卷

卷名内容丢失后果
imagehub_postgres_data全部元数据:用户、图片记录、相册、动态、设置记录全丢,图片变成孤儿文件
imagehub_storage_data原图、视频、缩略图缓存记录还在但文件打不开

06首次使用

  1. 打开站点并注册

    访问 http://localhost:3000,点击注册。空库状态下第一个注册的账号自动成为超级管理员(站长),后续可以在用户管理里给其他人分配角色。

  2. 处理首注册门控(公网部署必看)

    如果这台机器已经能被外网访问,强烈建议提前设置 SETUP_TOKEN。设置后,首次注册必须在请求里带上这个口令,否则任何人都可能抢先成为站长。

    # .env
    SETUP_TOKEN="自己定的一串口令"
  3. 进入站点设置

    超级管理员可在「站点设置」里配置:站点名称与备案信息、上传大小上限与配额、是否自动转 WebP、存储桶、邮件 SMTP、Redis 缓存、备份与还原。

  4. 上传第一张图片

    进入「上传」或直接在画廊拖拽文件,也可以粘贴剪贴板图片、填写图片 URL 抓取。上传完成后可在「我的图片」中调整可见性与所属相册。

07环境变量

完整清单见仓库根目录的 .env.example,每一项都有中文注释。关键项如下:

变量必填说明
SITE_URL是站点对外地址,用于生成站内链接,如 https://img.example.com
AUTH_SECRET是会话加密密钥,openssl rand -base64 32 生成
POSTGRES_PASSWORD是数据库密码,compose 用它拼接连接串
DATABASE_URL自动本地开发为 file:./dev.db;Docker 由 compose 注入
SETUP_TOKEN否首注册门控:空库首次注册必须携带,防公网抢注超管
BIND_IP否监听地址,默认 127.0.0.1;外网直连改 0.0.0.0
PORT否宿主机端口,默认 3000
MAX_UPLOAD_MB否图片单文件上限天花板(最大 512),实际生效取与界面设置的较小值;视频不设上限
CONVERT_TO_WEBP否是否把 jpg/png 自动转 WebP 存储,默认 false
LOCAL_STORAGE_DIR否本地存储目录,Docker 内固定为 /app/storage
REDIS_URL否限流计数落 Redis;后台开关打开时优先用设置里的地址
TRUST_PROXY否部署在反向代理后设为 1,按真实客户端 IP 限流

改完环境变量后需要重启容器:docker compose up -d。后台里能改的设置项(邮件、Redis 地址、存储桶等)则是即时生效的。

08存储与多桶

ImageHub 的存储模型是「位置即事实」:每张图片记录里都存着它真正所在的存储位置,读取时按记录去取,而不是假定所有文件在同一个地方。

本地磁盘

默认方案。文件落在 LOCAL_STORAGE_DIR(Docker 中是卷 imagehub_storage_data),备份、迁移、迁移都不依赖网络,速度最快。

添加 S3 兼容桶

在「站点设置 → 存储」里新增存储桶,需要填写:

字段说明
名称自定义标识,仅用于界面区分
Endpoint服务地址,如 R2 的 https://<account>.r2.cloudflarestorage.com
Bucket桶名
Region区域,部分服务商填 auto 即可
Access Key / Secret Key具备该桶读写权限的密钥
公开访问域名可选,配置后公开图片可直接走 CDN 域名

可以添加多个桶,并指定其中一个作为新上传的默认目标。已存在的图片不会因为改默认桶而移动,它们仍按各自记录的位置读取。

在存储之间迁移

「站点设置 → 存储迁移」支持在任意两个位置之间批量搬图,流程是:读源 → 写目标 → 更新记录 → 删源。任务具备幂等性,中断后重跑不会重复处理已完成的条目。

迁移前请确认目标桶的密钥有写入权限,并保证磁盘剩余空间足够。迁移过程中不要同时执行对账删除。

09存储对账

长期运行后,数据库记录与真实文件难免出现偏差:有人手工删过桶里的文件,或者旧版本删除时漏了清理。对账面板就是用来把两边对齐的。

双向核对

方向含义可执行动作
站有桶无数据库里有记录,但对应文件已不存在清理记录;或把这些记录对应的旧文件从别处「移入桶内」
桶有站无桶里存在文件,但没有任何记录引用它(孤儿)收录进站点成为正式图片;或从桶中删除

核对结果以左右两栏展示,点哪一边就显示哪一边的图片缩略图,支持翻页与勾选批量处理。所有删除动作都会在服务端复核引用:正被图片记录、相册封面或动态引用的对象不会被误删。

为什么有时看不到孤儿?后台有一个以「桶为事实源」的同步循环(约 30 秒一次),会把无人认领的文件自动收录成管理员图片。刚上传或刚删除的文件也会在 2 分钟内跳过判定,避免误伤正在进行的写入。

10邮件与通知

验证邮箱、重置密码、进画廊审核提醒、存储降级告警都靠邮件发出。未配置 SMTP 时,服务端会把邮件内容打印到容器日志,开发环境可直接从日志里取验证链接。

配置 SMTP

超级管理员在「站点设置 → 邮件配置」中填写,保存即生效,无需重启:

字段说明
SMTP 服务器发信服务器地址,如 smtp.example.com;留空表示未配置
端口常用 465(SSL)或 587(STARTTLS)
SSL / TLS与端口配套:465 通常开启,587 通常关闭(连接后升级加密)
账号 / 密码多数邮箱服务商要求填授权码,而不是登录密码
发件人如 ImageHub <[email protected]>,部分服务商要求与账号同域

连接状态

邮件配置页顶部有一枚状态徽标。打开页面、保存配置、点击「检测连接」都会重新探测一次:

  • 已连接:真实完成 TCP 连接与认证握手,当前配置可用。
  • 连接失败:旁边会附上服务商返回的具体错误,如授权码失效、账号被限制、端口不通。
  • 未配置:SMTP 服务器为空,邮件只会打印到日志。

SMTP 是「用完即断」的协议,没有常驻会话可以长期保持,所以这里的「已连接」是即时探测结果,而不是一次性的成功记录。这样一来,第一次配好、之后因授权码被改、服务商风控或网络策略变化而失效的情况,也能一眼看出来。检测过程只做连接与认证,不会发信。

发送测试邮件

「发送测试邮件」会按当前已保存的配置真实投递一封到全部超级管理员邮箱。收到即说明整条链路(连通、认证、发件人)都是通的;失败时页面会直接显示 SMTP 的原始报错。

常见失败原因:端口与 SSL 开关不匹配(465 未开 SSL、587 硬开 SSL)、填了登录密码而非授权码、发件人域名与账号不一致被拒收、云厂商默认封禁 25 端口。

11权限模型

三个角色,职责边界清晰。第一个注册的账号是超级管理员,其余角色由站长在用户管理里分配。

能力超级管理员管理员普通用户
用户管理 · 站点设置 · 存储迁移与备份可——
图片审核 · 删除违规动态与评论可可—
查看「日常记录」(动态照片汇总)可可—
管理自己的图片、相册与动态可可可
浏览他人私有图片可—需授权

角色变更后,对方重新登录才会生效 —— 会话里带的是签发时的角色。

普通用户能否看到你的私有图,取决于你把图片的可见性设为「公开」「友链」还是「私有」;「私有」只有你本人和超级管理员能看。

12上传机制

三种入口

  • 拖拽 / 选择文件:支持多选,并发上传并显示每个文件的进度。
  • 粘贴:截图后直接 Ctrl+V 即可上传。
  • URL 抓取:填远程图片地址,服务端拉取后存为站内图片(带 SSRF 防护)。

分片与秒传

超过 16MB 的文件自动切分为分片上传,中断后重试只补缺失的分片。上传前先算 sha256:若库里已有同一哈希的文件,直接建立新记录并复用已有二进制,也就是「秒传」。

分片上传的整个过程服务端内存占用恒为单个分片的大小,与文件总大小无关,因此低内存机器也能传大视频。

校验与处理

  • 魔数校验:按文件头判断真实类型,改扩展名伪造的文件会被拒绝。
  • EXIF 剥离:默认不保留拍摄地点等隐私信息。
  • 可选 WebP 转换:CONVERT_TO_WEBP=true 时把 jpg/png 转成 WebP 存储,节省空间。
  • 缩略图预热:上传完成即生成 400 档的 AVIF 与 WebP,首次浏览不必等现场转码。
  • 配额与上限:单文件上限取 MAX_UPLOAD_MB 与界面设置的较小值;账号配额满后会拒绝上传。
  • 视频:支持 mp4 / webm / mov / mkv,播放与拖动走流式 Range,不把整个文件读进内存。

14动态与日常记录

「动态」是朋友圈式的发布形式:一段文字 + 最多九张图,按时间倒序展示在 feed 里。

照片的归属规则

  • 发布动态时附带的照片,只出现在「动态」与「日常记录」两处。
  • 它们不会出现在「我的图片」、画廊与普通相册里,避免同一张照片在多个入口重复刷屏。
  • 「日常记录」是动态照片的汇总视图,带上传者与拍摄/上传时间,仅管理员与超级管理员可见。

日常记录页也支持直接上传照片:上传后自动生成一条动态,照片随即进入汇总,适合当作团队或家庭的流水记录使用。

15备份与还原

后台整包备份

超级管理员进入「站点设置 → 备份与还原」,可一键下载整包:包含数据库导出与全部存储文件(原图、视频、缩略图)。生成的包可以直接上传还原到另一台实例。

服务器侧备份

# 在安装目录执行
bash manage.sh backup        # 立即备份:db-时间戳.sql.gz + storage-时间戳.tar.gz
bash manage.sh cron          # 安装每日 04:00 自动备份
bash manage.sh restore       # 从备份列表中选择还原

备份文件默认落在安装目录的 backups/。务必定期把它们同步到另一台机器或异地存储 —— 和站点同盘备份无法应对磁盘损坏。

16运维命令

安装完成后,安装目录里会有 manage.sh,执行 bash manage.sh 进入交互菜单,也支持子命令直接调用。

#功能说明
1更新并清理旧镜像拉取最新镜像并重启,随后清理旧层
2卸载删除容器与全部数据卷(谨慎)
3更改端口修改宿主机监听端口
4修改站点地址同步更新 SITE_URL
5重置用户密码按邮箱重置为 Admin123456
6角色管理设为超管/管理员/普通用户,保证至少保留一个超管
7用户列表查看邮箱、角色、状态、图片数、注册日期
8服务控制启动 / 停止 / 重启
9实时日志跟随 web 容器最后 100 行输出
10备份数据数据库 + 存储文件
11还原数据从备份列表中选择
12每日自动备份安装 / 移除 04:00 定时任务
13对外开放切换在 127.0.0.1 与 0.0.0.0 之间切换
14清理旧镜像查看磁盘占用并清理本项目旧镜像
15站点信息地址、端口、版本、数据卷位置
16Redis 开关启用 / 停用 Redis 限流计数
17数据库控制台进入 psql 交互
18拉取新镜像只检查是否有更新,不重启
19强制重建容器配置文件改动后彻底重建

健康检查

curl http://localhost:3000/api/health
# {"ok":true,"db":"up"}

这个接口同时被 compose 的 healthcheck 使用。返回非 200 说明容器活着但数据库连不上,先看 docker compose logs postgres。

17反向代理与 HTTPS

官方默认把端口绑在本机回环,这是最省心的安全姿态。要让外网访问,推荐在宿主机前面放一层 Nginx 或 Caddy,由它负责 TLS。

Nginx 示例

server {
    listen 443 ssl http2;
    server_name img.example.com;

    ssl_certificate     /etc/letsencrypt/live/img.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/img.example.com/privkey.pem;

    # 视频与大文件上传,避免默认 1M 限制
    client_max_body_size 512m;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # 分片上传与视频流式播放需要关闭缓冲
        proxy_buffering off;
        proxy_request_buffering off;
    }
}

同时把 .env 里的 TRUST_PROXY 设为 1,并把 SITE_URL 改成 https://img.example.com,然后重启容器。这样限流会按真实客户端 IP 计数,站内生成的链接也不会退回 http。

不要把 /storage 目录用 Nginx alias 直接暴露出去。那会绕过站点的鉴权,私有图片等于公开。

18安全说明

  • 全站登录:页面与接口都经过中间件与会话校验,未登录无法访问任何图片。
  • 会话吊销:用户表带 tokenVersion,改密码或封禁后旧会话立即失效。
  • 登录限流:按 IP 与账号双维度计数,可落 Redis;反代部署请开 TRUST_PROXY 以免被伪造头绕过。
  • 首注册门控:SETUP_TOKEN 防止公网部署后超管被陌生人抢注。
  • SSRF 防护:URL 抓取只允许 http/https,拒绝内网地址与非常规端口。
  • 路径穿越防护:所有存储键做规范化校验,拒绝 .. 与绝对路径。
  • 类型校验:上传按魔数判断真实类型,不信任扩展名与 Content-Type。
  • 预览接口限制:只有超级管理员可用,且限定 images/ 与 videos/ 前缀。

发现安全问题请通过 GitHub Issues 或仓库主页的联系方式反馈,不要直接公开细节。

19常见问题

必须有自己的服务器吗?

需要一台能跑 Docker 的机器,家用小主机、NAS、云服务器都可以。默认只监听 127.0.0.1,本机或反代访问最安全。

图片存在哪里?会占用多少空间?

默认存本机磁盘(Docker 卷),也可以在站点设置里加对象存储桶,新上传进桶、旧文件仍按原位置读取。图片有配额上限,超出会被拒绝。空间占用约为原图总和 + 缩略图缓存。

我的图片会被公开吗?

默认私有。只有你显式选择公开、或把图片发到图文动态时才对外可见;公开直链可作外链,私有与下载接口始终需要登录。

忘记密码怎么办?

有邮件配置时走「忘记密码」;没有时用 bash manage.sh password 按邮箱重置为 Admin123456,登录后请立即修改。

删除图片后桶里的文件还在?

回收站阶段的删除只改状态,文件仍然保留以便还原。彻底删除才会清理文件。如果确认桶里有残留,用「存储对账」筛出「桶有站无」并批量删除即可。

能从本地磁盘迁移到对象存储吗?

可以。先在设置里添加并验证桶,再用「存储迁移」把既有图片批量搬到桶里,任务幂等,中断可重跑。

升级会不会丢数据?

数据在 Docker 卷里,升级只换镜像不动卷。仍建议升级前先 bash manage.sh backup 打一份备份。

支持视频吗?

支持 mp4 / webm / mov / mkv,播放与拖动走流式 Range,不把整个文件读进内存。视频不设单文件大小上限。

20目录结构

imagehub/
├── prisma/            # PostgreSQL / SQLite 两套 schema 与迁移
├── src/
│   ├── app/           # 页面与 API 路由(App Router)
│   ├── components/    # UI 组件
│   ├── server/        # 业务逻辑(图片 / 动态 / 迁移 / 对账 / 分片)
│   ├── lib/           # 会话、校验、存储驱动、缓存、限流
│   └── middleware.ts  # 访问控制
├── install.sh         # 一键安装脚本
├── manage.sh          # 站点管理脚本(19 项菜单)
├── Dockerfile
├── docker-compose.yml
└── .env.example

二次开发请从 src/server 入手看业务逻辑,从 src/app/api 看接口边界,从 prisma/schema.prisma 看数据模型。

本文档对应仓库 main 分支。若界面与描述有出入,以实际版本与 .env.example 注释为准,也欢迎在 GitHub 提 issue 指出。