基于 rclone + systemd + 本地索引 JSON 的自建图床完整方案

176次阅读
没有评论
内容纲要

## 一、背景与目标

我在 Claw Cloud(S3 兼容对象存储)上创建了一个 bucket,并绑定自定义域名:

```text
https://images.isrv.cn/
```

目标是构建一个:

- 本地可管理(预览 / 重命名 / 分类)

- 自动同步到对象存储

- 支持直链访问

- 带图床首页(可浏览、搜索、复制 Markdown)

- 完全静态(无后端服务)

---

## 二、最终架构

```text
📁 /home/ding/Pictures/Images ← 本地主库

🐍 generate-images-json.py ← 本地生成索引 JSON

🔁 rclone sync (systemd timer)

☁️ Claw S3 bucket

🌍 https://images.isrv.cn/
```

核心思想:

> **本地目录是唯一真源,远端只负责发布和访问**

---

## 三、目录结构

```text
/home/ding/Pictures/Images/
├── index.html # 图床首页
├── images.json # 本地生成的索引
├── screenshots/
├── posts/
├── wallpapers/
└── ...
```

---

## 四、rclone 配置

已配置远端(示例):

```bash
rclone ls claw:ujwn4e6y-img
```

说明:

- `claw`:远端名称

- `ujwn4e6y-img`:bucket 名

---

## 五、生成图片索引(核心)

### 1. 脚本路径

```bash
/home/ding/.local/bin/generate-images-json.py
```

### 2. 完整代码

```python
#!/usr/bin/env python3
from __future__ import annotations

import json
import mimetypes
from pathlib import Path
from urllib.parse import quote

ROOT = Path("/home/ding/Pictures/Images")
BASE_URL = "https://images.isrv.cn"
OUTPUT = ROOT / "images.json"

IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg", ".avif"}

def is_image(path: Path) -> bool:
if path.name in {"index.html", "images.json"}:
return False
return path.suffix.lower() in IMAGE_EXTS

def main():
items = []

for path in ROOT.rglob("*"):
if not path.is_file():
continue

# 忽略隐藏文件
if any(part.startswith(".") for part in path.relative_to(ROOT).parts):
continue

if not is_image(path):
continue

rel = path.relative_to(ROOT).as_posix()
stat = path.stat()

items.append({
"name": path.name,
"path": rel,
"url": f"{BASE_URL}/{quote(rel, safe='/')}",
"mtime": int(stat.st_mtime),
"size": stat.st_size,
"type": mimetypes.guess_type(path.name)[0] or "application/octet-stream",
})

# 按修改时间倒序(最新优先)
items.sort(key=lambda x: x["mtime"], reverse=True)

with OUTPUT.open("w", encoding="utf-8") as f:
json.dump(items, f, ensure_ascii=False, separators=(",", ":"))

if __name__ == "__main__":
main()
```

### 3. 赋权

```bash
chmod +x /home/ding/.local/bin/generate-images-json.py
```

---

## 六、同步脚本

### 1. 路径

```bash
/home/ding/.local/bin/rclone-images-sync.sh
```

### 2. 完整代码

```bash
#!/usr/bin/env bash
set -euo pipefail

SRC="/home/ding/Pictures/Images"
DST="claw:ujwn4e6y-img"
LOG="/home/ding/.local/state/rclone-images-sync.log"
GEN="/home/ding/.local/bin/generate-images-json.py"

mkdir -p "$(dirname "$LOG")"

# 先生成 JSON 索引
"$GEN"

# 再同步到对象存储
exec /usr/bin/rclone sync "$SRC" "$DST" \
--fast-list \
--transfers 8 \
--checkers 16 \
--delete-during \
--track-renames \
--log-file "$LOG" \
--log-level INFO
```

### 3. 赋权

```bash
chmod +x /home/ding/.local/bin/rclone-images-sync.sh
```

---

## 七、systemd 定时任务

### 1. service

路径:

```bash
~/.config/systemd/user/rclone-images-sync.service
```

内容:

```ini
[Unit]
Description=Sync local image directory to Claw object storage
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/home/ding/.local/bin/rclone-images-sync.sh
```

---

### 2. timer

路径:

```bash
~/.config/systemd/user/rclone-images-sync.timer
```

内容:

```ini
[Unit]
Description=Run rclone image sync periodically

[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
Persistent=true

[Install]
WantedBy=timers.target
```

---

### 3. 启用

```bash
systemctl --user daemon-reload
systemctl --user enable --now rclone-images-sync.timer
```

查看:

```bash
systemctl --user list-timers
```

---

### 4. 关键(保持后台运行)

```bash
loginctl enable-linger ding
```

---

## 八、图床首页(index.html)

路径:

```bash
/home/ding/Pictures/Images/index.html
```

(此处省略代码,已在上一条提供完整版本)

----

功能:

- 图片网格展示

- 按时间排序(最新优先)

- 搜索(文件名 / 路径)

- 分页

- 点击缩略图 → 当前页打开(支持 Alt+← 返回)

- 预览按钮 → 新标签打开

- 复制直链

- 复制 Markdown

---

## 九、访问方式

### 单图访问

```text
https://images.isrv.cn/screen_20260329_195241.png
```

### 图床首页

```text
https://images.isrv.cn/
```

---

## 十、验证流程

### 手动执行同步

```bash
/home/ding/.local/bin/rclone-images-sync.sh
```

### 查看远端

```bash
rclone ls claw:ujwn4e6y-img
```

### 浏览器访问

```text
https://images.isrv.cn/
```

---

## 十一、关键设计决策

### 1. 为什么不用 S3 挂载

- 非 POSIX 文件系统

- 预览体验差

- 性能差

- 行为不稳定

👉 放弃 mount,采用直传

---

### 2. 为什么不用 rsync

- rsync 适用于文件系统

- 不理解对象存储语义

👉 使用 rclone 原生支持

---

### 3. 为什么本地生成 JSON

对象存储:

- 不支持目录列表

- 不支持动态查询

👉 本地生成索引 → 静态加载

---

### 4. 为什么用 systemd timer

相比 cron:

- 支持开机补执行(Persistent)

- 更好日志管理

- 可观测性强

---

### 5. 为什么用 sync 而不是 copy

```bash
rclone sync
```

保证:

- 本地删除 → 远端删除

- 本地修改 → 远端更新

👉 保持完全一致

---

## 十二、注意事项

### ⚠️ 删除风险

`sync` 会删除远端文件:

> 本地误删 = 远端也会删除

建议:

- 初期使用 `--dry-run`

- 或增加备份策略

---

### ⚠️ 文件命名建议

推荐:

```text
screen_20260329_195241.png
autumn-leaves.png
post-cover.jpg
```

避免:

- 空格

- 中文(可用但不推荐)

- 特殊符号

---

### ⚠️ URL 编码

已在 Python 中处理:

```python
quote(rel, safe='/')
```

---

## 十三、最终效果

你现在拥有:

- ✔ 本地文件管理体验(完全自由)

- ✔ 自动同步到对象存储

- ✔ 自定义域名访问

- ✔ 可浏览的图床首页

- ✔ 一键复制 Markdown

- ✔ 完全静态,无后端依赖

---

## 十四、一句话总结

> **用本地目录作为主库 + rclone 定时同步 + 本地生成 JSON 索引 + 静态页面渲染,就是一个稳定、可控、零后端的图床方案。**

---

如果以后需要继续升级,这一套可以自然扩展到:

- 自动压缩 / WebP

- Markdown 自动上传工具(类似 PicGo)

- 私有签名 URL

- CDN 加速策略

但就目前而言,这套已经是**工程上非常干净且稳定的方案**了。

正文完
 0
评论(没有评论)