From ff20f97522d5f3e32e4e234a119bc5099f45b768 Mon Sep 17 00:00:00 2001
From: O96u
- Branches · - Quick Start · + Docs · Screenshots · Features · - Tech Stack · - Roadmap · + Quick Start · 简体中文
--- -## Branches - -| Branch | Notes | -| ------ | ----- | -| [**main**](https://github.com/O96u/PicHost/tree/main) (default) | **v1.2.0** (in progress) — unified `images/` storage, multi-backend, dual-domain isolation, [`/storage`](docs/README.md), [`migration guide`](docs/migration-to-v1.2.en.md) | -| [**cloudflare**](https://github.com/O96u/PicHost/tree/cloudflare) | **Cloudflare R2–only line** — streamlined for deployments that use R2 exclusively | - -Use **main** for the full product and multiple cloud backends. - -If you **only use Cloudflare R2**, check out the dedicated branch: - -```bash -git clone https://github.com/O96u/PicHost.git -cd PicHost -git checkout cloudflare -``` - ---- - ## Screenshots -| Upload | Storage | -| :------------------------------------: | :--------------------------------: | -|  |  | +**main** has no public demo. **cloudflare** branch live demo: [pic.roven.cc](https://pic.roven.cc) -| Stats & gallery | Settings | -| :----------------------------------: | :--------------------------------: | -|  |  | +| Home | Storage | +| :--: | :-----: | +|  |  | + +| Stats | Settings | +| :---: | :-------: | +|  |  | ## Features -- **Drag, click, or Ctrl+V paste** to upload; all files under `images/` -- **Server-side WebP** (sharp), Referer hotlink protection, configurable public base URL -- **Multi-user** — username/password login; optional registration; users see only their images -- **Upload preferences** (home card flip): client-side compression, auto-copy links, per-user auto-delete -- **Multi-backend storage**: local disk + S3-compatible (Cloudflare R2 / Tencent COS / Alibaba OSS / AWS S3); admins manage backends at **Storage** (`/storage`) -- **Hybrid URLs**: `proxy` (same-origin via PicHost) or `public` (302 to CDN / bucket URL) -- **Gallery** — browse, search, batch delete; admin sees uploader badges -- **Stats** — upload/delete metrics, upload source breakdown; admin sees registered user count -- **API** — global token (admin) + per-user tokens; in-app cURL snippets -- **Twikoo** — `POST /api/index.php` (`image` + `token`) -- **Zero-config Docker** — first-run web wizard, no secrets required upfront +- **Drag, click, or Ctrl+V paste** — server-side WebP, Referer hotlink protection +- **Multi-user** — login with slider captcha, optional registration; users see only their images +- **Multi-backend storage** — local disk + S3-compatible (R2 / COS / OSS / AWS); hybrid `proxy` / `public` URLs +- **Gallery & stats** — browse, search, batch delete; upload trends and source breakdown +- **API & Twikoo** — global / per-user tokens; `POST /api/index.php` compatible +- **Zero-config Docker** — first-run web wizard, no secrets upfront ## Quick Start -### Docker (recommended) - ```bash docker run -d \ --name pichost \ @@ -84,184 +58,14 @@ docker run -d \ muxui/pichost:latest ``` -Default port: **6892**. Open `http://- 分支 · - 快速开始 · + 文档 · 预览 · 特性 · - 技术栈 · - 路线图 · + 快速开始 · English
--- -## 分支 - -| 分支 | 说明 | -| ---- | ---- | -| [**main**](https://github.com/O96u/PicHost/tree/main)(默认) | **v1.2.0**(开发中)— 统一 `images/` 存储、多后端、双域名隔离、[`/storage`](docs/README.md)、[`迁移指南`](docs/migration-to-v1.2.md) | -| [**cloudflare**](https://github.com/O96u/PicHost/tree/cloudflare) | **Cloudflare R2 专用线**:面向「只用 R2」的部署,预设与配置更聚焦 R2 | - -日常使用、需要管理多种存储后端,请直接用 **main**。 - -若图床**只接 Cloudflare R2**、希望更简化的 R2 专用版本,可切换分支: - -```bash -git clone https://github.com/O96u/PicHost.git -cd PicHost -git checkout cloudflare -``` - ---- - ## 界面预览 -| 上传 | 存储管理 | -| :-----------------------------------: | :-------------------------------------: | -|  |  | +**main** 分支暂无公网演示;**cloudflare** 分支在线体验:[pic.roven.cc](https://pic.roven.cc) -| 统计与图库 | 系统设置 | -| :---------------------------------: | :-------------------------------------: | +| 首页 | 存储 | +| :--: | :--: | +|  |  | + +| 统计 | 设置 | +| :--: | :--: | |  |  | ## 特性 -- **拖拽 / 点击 / Ctrl+V 粘贴**上传;图片统一存入 `images/` -- **服务端 WebP 压缩**(sharp)、Referer 防盗链、可配置访问域名 -- **多用户**:账号密码登录;管理员可开放注册;普通用户仅见自己的图片 -- **上传偏好**(首页卡片翻转):客户端预压缩、自动复制链接、按用户自动删除 -- **多后端存储**:本地磁盘 + S3 兼容(Cloudflare R2 / 腾讯云 COS / 阿里云 OSS / AWS S3);管理员在 **存储**(`/storage`)页管理,新上传写入默认后端 -- **混合直链**:`proxy`(PicHost 同源代理)或 `public`(302 到 CDN / 桶公网地址) -- **图库**:浏览、搜索、批量删除;管理员可查看上传者标签 -- **统计**:上传/删除趋势、上传来源分布;管理员额外显示注册用户数量 -- **API**:全局 Token(管理员)+ 每用户个人 Token;后台可复制 cURL -- **Twikoo**:`POST /api/index.php`(`image` + `token`) -- **Docker 零配置**:首次访问 Web 引导创建管理员,无需预先写密钥 +- **拖拽 / 点击 / Ctrl+V 粘贴**上传;服务端 WebP 压缩、Referer 防盗链 +- **多用户**:账号密码登录(滑动验证)、可选开放注册;普通用户仅见自己的图片 +- **多后端存储**:本地磁盘 + S3 兼容(R2 / COS / OSS / AWS);混合直链 `proxy` / `public` +- **图库与统计**:浏览、搜索、批量删除;上传趋势与来源分布 +- **API 与 Twikoo**:全局 / 个人 Token;`POST /api/index.php` 兼容 +- **Docker 零配置**:首次访问 Web 引导创建管理员 ## 快速开始 -### Docker(推荐) - ```bash docker run -d \ --name pichost \ @@ -84,191 +58,14 @@ docker run -d \ muxui/pichost:latest ``` -默认端口 **6892**。浏览器打开 `http://<主机IP>:6892`,按引导创建管理员即可。 +浏览器打开 `http://<主机IP>:6892`,按引导完成设置即可。 -**忘记密码**(需能执行 `docker exec`,相当于服务器权限): +**详细说明**(环境变量、升级迁移、双域名、API、Twikoo、反向代理、本地开发等)见 **[用户文档](https://o96u.github.io/PicHost/)**。 -```bash -# 无参数:重置管理员(仅当系统中只有一个管理员时) -docker exec pichost reset-password - -# 指定用户名:可重置管理员或普通用户 -docker exec pichost reset-password 用户名 -``` - -终端会打印随机新密码;用户不存在时会报错。登录后请到「修改密码」更换。 - -本地:`npm run reset-password` 或 `npm run reset-password -- 用户名` - -### 升级到 v1.2.0(本地磁盘遗留目录) - -v1.2.0 起图片统一在 `data/images/`。迁移分两步: - -1. **CLI 搬文件**(手动,升级前后均可):把 `data/` 下除 `images` 外各顶层目录里的图片迁入 `data/images/{原目录名}/...` -2. **启动时同步索引**(自动):扫描 `data/images/` 补全 SQLite、归一化遗留 key、清理无文件的孤儿记录 - -```bash -# 1. 预览迁移并生成 data/mapping.json(旧 key → 新 key) -docker exec pichost migrate - -# 2. 确认后执行文件移动 -docker exec pichost migrate --apply - -# 3. 升级镜像并重启;启动日志会打印索引同步过程 -``` - -本地:`npm run migrate` / `npm run migrate -- --apply`。 - -详细说明(结构对比、日志含义、外链替换、**其它图床迁入**):[`docs/migration-to-v1.2.md`](docs/migration-to-v1.2.md) - -仅对象存储、或图片已在 `data/images/` 下时,可跳过 CLI,仅依赖启动同步。 - -已克隆仓库时也可用 `docker compose up -d`(见仓库内 `docker-compose.yml`)。 - -### 本地开发 - -```bash -npm install -cp .env.example .env # 可选 -npm run dev -``` - -访问 `http://localhost:3000/setup` 创建管理员;数据目录默认 `./data`。 - -## 技术栈 - -| 层级 | 技术 | -| -------- | ------------------------------------------ | -| 前端 | Nuxt 4、Vue 3、Nuxt UI 4、Tailwind CSS 4 | -| 后端 | Nitro(Node.js)、SQLite | -| 图片处理 | sharp(WebP 转码与压缩) | -| 鉴权 | Session Cookie、scrypt 密码哈希、API Token | -| 存储 | 本地磁盘 + S3 兼容后端(R2 / COS / OSS / AWS);`images` 索引在 SQLite | -| 部署 | Docker(amd64 / arm64)、docker compose | -| 测试 | Vitest | - -## 用户与权限 - -| 场景 | 鉴权 | 图片归属 / 可见范围 | -| -------------------------- | -------------------- | -------------------------- | -| 网页上传 | Session | 当前登录用户 | -| API 上传 + **个人 Token** | `Auth-Token` | 该 Token 所属用户 | -| API 上传 + **全局 Token** | `Auth-Token` | 管理员(`userId` 为空) | -| Twikoo `/api/index.php` | 表单 `token` | 与全局 Token 相同 | -| 图库列表 / 搜索 / 删除 | Session 或 Token | 普通用户仅自己;管理员全部 | -| 图片直链 `GET /images/...` | 无(Referer 防盗链) | 知道 URL 即可访问 | - -所有上传统一存入 `images/` 目录。 - -## 存储结构 - -``` -/data -├── pichost.db # SQLite:用户、会话、设置、storage_backends、images 索引 -└── images/ # 全部图片(本地后端时);可含迁移子路径如 file/、年/月/日/ -``` - -路径规则:新上传为 `images/随机ID.webp` 或 `images/年/月/随机ID.webp`;迁移遗留可为更深子路径(如 `images/blog/...`)。元数据在 SQLite `images` 表;云存储时文件在对应桶内。备份请包含 `data/images/`、`pichost.db` 与云桶数据。 - -从 v1.1.x 升级且存在 `data/blog/` 等并列目录时,见 [升级到 v1.2.0](#升级到-v120本地磁盘遗留目录) 或 [`docs/migration-to-v1.2.md`](docs/migration-to-v1.2.md)。 - -## API 概览 - -| 方法 | 路径 | 说明 | -| ------ | -------------------- | ---------------------- | -| POST | `/api/images/upload` | 上传图片 | -| GET | `/api/images` | 分页列表 | -| DELETE | `/api/images` | 删除单张(`key`) | -| POST | `/api/index.php` | Twikoo / EasyImage 2.0 | - -上传示例: - -```bash -curl -X POST "https://pic.example.com/api/images/upload" \ - -H "Auth-Token: YOUR_TOKEN" \ - -F "image=@./demo.png" -``` - -完整接口与可复制 cURL 见部署后后台 **API** 页。 - -## Twikoo - -| 配置项 | 值 | -| ----------------- | -------------------------------- | -| `IMAGE_CDN` | `easyimage` | -| `IMAGE_CDN_URL` | `https://你的域名/api/index.php` | -| `IMAGE_CDN_TOKEN` | 与 `API_UPLOAD_TOKEN` 相同 | - -## 反向代理 - -- 反代到容器 **6892** 端口,建议 HTTPS -- Nginx:`client_max_body_size` ≥ 12m -- 公网访问请配置 `IMAGE_BASE_URL`(单域名)或 `SITE_BASE_URL` + `IMAGE_BASE_URL`(双域名分离) - -### 推荐部署架构(双域名分离) - -**单 Docker 实例 + 两个域名** 均可全量反代到 `6892`(隔离由 PicHost 中间件负责): - -```nginx -# admin.example.com — 后台 + API + 出图 -server { - server_name admin.example.com; - location / { - proxy_pass http://127.0.0.1:6892; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-Proto $scheme; - client_max_body_size 12m; - } -} - -# pic.example.com — 同样全量反代;PicHost 中间件会拦截非图片路径 -server { - server_name pic.example.com; - location / { - proxy_pass http://127.0.0.1:6892; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -``` - -飞牛 NAS [Lucky](https://github.com/gdy666/lucky) 反向代理示例(前端域名 → 后端 `http://<内网IP>:6892`): - -| 网站域名(管理后台) | 图片域名 | -| :------------------: | :------: | -|  |  | - -完整说明(Caddy / NPM、环境变量、初始化填写项)见 **[docs/domain-separation.md](docs/domain-separation.md)**。 - -## 开发 - -```bash -npm run dev # 开发服务器 -npm run build # 生产构建 -npm run start # 运行 .output -npm run lint # ESLint -npm run typecheck # 类型检查 -npm test # 单元测试 -npm run reset-password # 重置密码(可选用户名参数) -npm run migrate # 预览遗留目录迁移(加 -- --apply 执行) -``` - -## 路线图 - -| 版本 | 说明 | -| ---------- | ------------------------------------------------- | -| **v1.2.0** | 统一 `images/` 存储、遗留目录 CLI 迁移、启动索引同步、上传来源统计(开发中) | -| **v1.1.5** | 图库预览可靠性:自动重试、出图前校验、错误响应禁缓存 | -| **v1.1.4** | 存储路径扁平/分组、basename 外链、双域名双向隔离 | -| **v1.1.3** | 双域名分离、隐藏 `images/` 前缀、版本更新提示 | -| **v1.1.2** | 操作日志、图库存储筛选、上传限流 | -| **v1.1.1** | 会话校验与存储占位修复、Docker 日志增强 | -| **v1.1.0** | S3 / R2 多后端对象存储、混合直链、存储管理页 | -| **v1.0.4** | 密码显示切换、遗留 ADMIN_SECRET 迁移对齐 | -| **v1.0.3** | CI 与 Docker 多架构构建修复 | -| **v1.0.2** | 中英文、移动端菜单、统计/上传 UI、Docker 重置密码 | -| **v1.0.1** | 设置页版本展示、移动端上传偏好布局修复 | -| **v1.0.0** | 本地存储、多用户、API、Twikoo | +| 分支 | 说明 | +| ---- | ---- | +| [**main**](https://github.com/O96u/PicHost/tree/main) | 默认主线:多后端、双域名、统一 `images/` 存储 | +| [**cloudflare**](https://github.com/O96u/PicHost/tree/cloudflare) | 仅 Cloudflare R2;在线演示 [pic.roven.cc](https://pic.roven.cc) | ## License diff --git a/app/components/AdminLoginGate.vue b/app/components/AdminLoginGate.vue index b0d2e03..663a5d6 100644 --- a/app/components/AdminLoginGate.vue +++ b/app/components/AdminLoginGate.vue @@ -11,14 +11,29 @@ const username = ref('') const password = ref('') const secret = ref('') const loading = ref(false) +const captchaRef = ref<{ reset: () => Promise+ {{ t('auth.captchaHint') }} +
+ ++ {{ t('auth.captchaSuccess') }} +
+ +
{{ t('setup.domainSeparationProxyHint') }}
1) {
+ await goToPage(galleryPage.value - 1)
+ }
}
if (failed.length) {
diff --git a/docs-site/.vitepress/config.ts b/docs-site/.vitepress/config.ts
new file mode 100644
index 0000000..0920183
--- /dev/null
+++ b/docs-site/.vitepress/config.ts
@@ -0,0 +1,129 @@
+import { defineConfig } from 'vitepress'
+
+const docsBase = 'https://o96u.github.io/PicHost'
+
+const zhSidebar = [
+ {
+ text: '开始',
+ items: [
+ { text: '文档首页', link: '/guide/' },
+ { text: '快速开始', link: '/guide/getting-started' },
+ { text: '本地开发', link: '/guide/local-dev' }
+ ]
+ },
+ {
+ text: '部署与配置',
+ items: [
+ { text: '环境变量', link: '/guide/configuration' },
+ { text: '反向代理', link: '/guide/reverse-proxy' },
+ { text: '双域名分离', link: '/guide/domain-separation' }
+ ]
+ },
+ {
+ text: '使用',
+ items: [
+ { text: '存储', link: '/guide/storage' },
+ { text: '用户与权限', link: '/guide/users-and-permissions' },
+ { text: 'API', link: '/guide/api' },
+ { text: 'Twikoo', link: '/guide/twikoo' }
+ ]
+ },
+ {
+ text: '升级',
+ items: [
+ { text: 'v1.2 迁移', link: '/guide/migration' },
+ { text: '常见问题', link: '/guide/faq' }
+ ]
+ }
+]
+
+const enSidebar = [
+ {
+ text: 'Getting started',
+ items: [
+ { text: 'Overview', link: '/en/guide/' },
+ { text: 'Quick start', link: '/en/guide/getting-started' },
+ { text: 'Local development', link: '/en/guide/local-dev' }
+ ]
+ },
+ {
+ text: 'Deploy & configure',
+ items: [
+ { text: 'Environment variables', link: '/en/guide/configuration' },
+ { text: 'Reverse proxy', link: '/en/guide/reverse-proxy' },
+ { text: 'Dual-domain separation', link: '/en/guide/domain-separation' }
+ ]
+ },
+ {
+ text: 'Usage',
+ items: [
+ { text: 'Storage', link: '/en/guide/storage' },
+ { text: 'Users & permissions', link: '/en/guide/users-and-permissions' },
+ { text: 'API', link: '/en/guide/api' },
+ { text: 'Twikoo', link: '/en/guide/twikoo' }
+ ]
+ },
+ {
+ text: 'Upgrade',
+ items: [
+ { text: 'v1.2 migration', link: '/en/guide/migration' },
+ { text: 'FAQ', link: '/en/guide/faq' }
+ ]
+ }
+]
+
+export default defineConfig({
+ base: '/PicHost/',
+ title: 'PicHost',
+ description: '自托管图床文档 · Self-hosted image hosting docs',
+ head: [['link', { rel: 'icon', href: '/PicHost/favicon.ico' }]],
+ locales: {
+ root: {
+ label: '简体中文',
+ lang: 'zh-CN',
+ themeConfig: {
+ nav: [
+ { text: '指南', link: '/guide/getting-started' },
+ { text: 'API', link: '/guide/api' },
+ { text: 'GitHub', link: 'https://github.com/O96u/PicHost' }
+ ],
+ sidebar: zhSidebar,
+ docFooter: { prev: '上一页', next: '下一页' },
+ outline: { label: '本页目录' },
+ lastUpdated: { text: '最后更新' },
+ returnToTopLabel: '回到顶部',
+ sidebarMenuLabel: '菜单',
+ darkModeSwitchLabel: '主题',
+ langMenuLabel: '语言'
+ }
+ },
+ en: {
+ label: 'English',
+ lang: 'en-US',
+ link: '/en/',
+ themeConfig: {
+ nav: [
+ { text: 'Guide', link: '/en/guide/getting-started' },
+ { text: 'API', link: '/en/guide/api' },
+ { text: 'GitHub', link: 'https://github.com/O96u/PicHost' }
+ ],
+ sidebar: enSidebar,
+ docFooter: { prev: 'Previous', next: 'Next' },
+ outline: { label: 'On this page' },
+ lastUpdated: { text: 'Last updated' },
+ returnToTopLabel: 'Back to top',
+ sidebarMenuLabel: 'Menu',
+ darkModeSwitchLabel: 'Appearance',
+ langMenuLabel: 'Language'
+ }
+ }
+ },
+ themeConfig: {
+ socialLinks: [{ icon: 'github', link: 'https://github.com/O96u/PicHost' }],
+ search: { provider: 'local' },
+ editLink: {
+ pattern: `${docsBase}/edit/main/docs-site/:path`,
+ text: '在 GitHub 上编辑此页'
+ }
+ }
+})
diff --git a/docs-site/.vitepress/theme/custom.css b/docs-site/.vitepress/theme/custom.css
new file mode 100644
index 0000000..e5a92b7
--- /dev/null
+++ b/docs-site/.vitepress/theme/custom.css
@@ -0,0 +1,6 @@
+:root {
+ --vp-c-brand-1: #22c55e;
+ --vp-c-brand-2: #16a34a;
+ --vp-c-brand-3: #15803d;
+ --vp-c-brand-soft: rgba(34, 197, 94, 0.14);
+}
diff --git a/docs-site/.vitepress/theme/index.ts b/docs-site/.vitepress/theme/index.ts
new file mode 100644
index 0000000..42fe9a9
--- /dev/null
+++ b/docs-site/.vitepress/theme/index.ts
@@ -0,0 +1,4 @@
+import DefaultTheme from 'vitepress/theme'
+import './custom.css'
+
+export default DefaultTheme
diff --git a/docs-site/copy-screenshots.mjs b/docs-site/copy-screenshots.mjs
new file mode 100644
index 0000000..6a0c93f
--- /dev/null
+++ b/docs-site/copy-screenshots.mjs
@@ -0,0 +1,16 @@
+import { cpSync, existsSync, mkdirSync } from 'node:fs'
+import { dirname, join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const root = join(dirname(fileURLToPath(import.meta.url)), '..')
+const src = join(root, 'docs', 'screenshots')
+const dest = join(root, 'docs-site', 'public', 'screenshots')
+
+if (!existsSync(src)) {
+ console.error(`Screenshot source missing: ${src}`)
+ process.exit(1)
+}
+
+mkdirSync(dest, { recursive: true })
+cpSync(src, dest, { recursive: true })
+console.log(`Copied screenshots to ${dest}`)
diff --git a/docs-site/en/guide/api.md b/docs-site/en/guide/api.md
new file mode 100644
index 0000000..921eee2
--- /dev/null
+++ b/docs-site/en/guide/api.md
@@ -0,0 +1,99 @@
+# API
+
+PicHost exposes REST endpoints and a Twikoo-compatible upload. After deployment, open the in-app **API** page for your token and copyable cURL snippets.
+
+## Authentication
+
+All REST calls use:
+
+```http
+Auth-Token: YOUR_TOKEN
+```
+
+| Token type | How to obtain | Notes |
+| ---------- | ------------- | ----- |
+| **Global** | Admin **API** page or `API_UPLOAD_TOKEN` env | Env wins and blocks UI regenerate |
+| **Personal** | Each user’s **API** page | Uploads belong to that user |
+
+Form uploads may use field `token` (Twikoo protocol).
+
+## Endpoints
+
+### 1. Upload images
+
+`POST /api/images/upload`
+
+Upload one or more images via `image`; also accepts `file`, `files`. Stored under `images/`.
+
+```bash
+curl -X POST "https://admin.example.com/api/images/upload" \
+ -H "Auth-Token: YOUR_TOKEN" \
+ -F "image=@./demo.png"
+```
+
+### 2. List images
+
+`GET /api/images`
+
+Paginated gallery. `limit` default 20, max 100.
+
+```bash
+curl "https://admin.example.com/api/images?limit=20&page=1" \
+ -H "Auth-Token: YOUR_TOKEN"
+```
+
+### 3. Search images
+
+`GET /api/images/search`
+
+Search by filename or path; `q` is required.
+
+```bash
+curl "https://admin.example.com/api/images/search?q=demo&limit=20&page=1" \
+ -H "Auth-Token: YOUR_TOKEN"
+```
+
+### 4. Delete image
+
+`DELETE /api/images`
+
+Delete by `key` (storage path, e.g. `images/2026/08/xxx.webp`).
+
+```bash
+curl -X DELETE "https://admin.example.com/api/images?key=images/2026/08/xxxx.webp" \
+ -H "Auth-Token: YOUR_TOKEN"
+```
+
+### 5. Batch delete
+
+`POST /api/images/batch-delete`
+
+JSON body: `{"keys":["images/2026/08/a.webp","images/2026/08/b.webp"]}`
+
+```bash
+curl -X POST "https://admin.example.com/api/images/batch-delete" \
+ -H "Auth-Token: YOUR_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"keys":["images/2026/08/a.webp","images/2026/08/b.webp"]}'
+```
+
+## Errors
+
+REST responses are JSON with `code` and `message` (`UNAUTHORIZED`, `FORBIDDEN`, `INVALID_REQUEST`, etc.). Twikoo uses EasyImage-compatible JSON.
+
+## Visibility
+
+- User token: list/search/delete own images only
+- Admin token: all images
+- Global token uploads belong to admin
+
+See [Users & permissions](./users-and-permissions.md).
+
+## Twikoo
+
+`POST /api/index.php` — [Twikoo](./twikoo.md).
+
+## See also
+
+- [Environment variables](./configuration.md)
+- [Reverse proxy](./reverse-proxy.md)
diff --git a/docs-site/en/guide/configuration.md b/docs-site/en/guide/configuration.md
new file mode 100644
index 0000000..f9824d7
--- /dev/null
+++ b/docs-site/en/guide/configuration.md
@@ -0,0 +1,101 @@
+# Environment variables
+
+Configure PicHost via environment variables (Docker / `.env`) or the in-app **Settings** page. Most values prefer **SQLite** when saved; some env vars override or lock UI editing.
+
+## Priority overview
+
+| Setting | Priority (high → low) | Env overrides UI |
+| ------- | --------------------- | ---------------- |
+| API upload token | Env → SQLite | **Yes** (cannot regenerate in UI) |
+| WebP quality | SQLite → env → default 80 | No |
+| Referer whitelist | SQLite → env | No |
+| Site / image base URL | SQLite → env → request origin | No |
+| Hide `images/` prefix | SQLite → env → default false | No |
+| Date-based paths | SQLite → env → default true | No |
+| Global auto-delete days | SQLite → env → default 0 | No |
+
+## Variables
+
+### `API_UPLOAD_TOKEN`
+
+Token for external uploads (Twikoo, userscripts, blogs). Header: `Auth-Token: g-jkIS6}#YC-HCyTICk9P%u8z518d;q~ul
zpQ8NIut*EbJX$vC958q4$v1-&7?T8O5!tTg%p6!zTT(|wni7P#Oz**y2oICpzmo)_
z%k *l$<-sst?qQrp8_LvIW9EH61|jf&^)M6`Yypde@D
zrzz>qIu4sjy0`I`Df6i$sqp=?qnk@|Cz#D=K1;e3K55UfL+Jns4r9d)^^XCu!RlCJU+mvqcI>jdD60C~*I+Q$X)$Z1#
zYw~BV=Q_f7$>JfksBq9wB?wMWtwq}mtjN;qAV`vB)hl3J)N;y(kpMK{V~^Wqd164)
zcR!>WXJ2Y)A2mLH%}qzT2BG^XZ1r4ghz<`*`F!1;>UMvy!Soxz#dlZtt@D3YIHtL-
z4_@692zz=b
IE|7Uf2Q^BDI6nH)^~X#z}JmiD8wc
zv5Fs3r!oQyXFlO~g?<2jQAY#*qQ$1WRwY*_IGjhbL%6H_#VP}kG*07k`q?yhCyP`g
zF{p`w|6``%p2_YEf4v|Kf5|kTOjWX1D#y9P@wWZ`k6&4=mWux{cgBZQ!hAMQ^Vi$w
z6i1emw#$j`C6;L}=I1LmBc!EOc*(Vs5@CnrBIa71_i1!^5=o74&eB&FN>Ouu{GMgtQN)ih@$!HN`*aZP+8ljAvxO$
z4JlG@&!~MD?}Qc>1^CX@0*$4bt3N?QClW`lYnJ*+p`r(HwPF&oTS1I@=LE~Zq$|av
zPWpQ6$#v=*b+g1$!=Z7w*NGN8CH@EYg${yg^qC_@!4H>=EUSMD40(w&1)Ia2Ny9y<
z83SX;z%(mFna6bc;75HQGZ!|oBVnP^9HS=s5(SS^Pi2oUK&0aF8%WKsHy}WywZAM&
zd#Tg4kw(TtHSWQ4mS^b0q7WY!LbtzZ1#x>B%?zz6Oj|~Sph+tracJy(~y@hSZ+sCIMc0AXfMBPnMH*}LvGus@*dL62l_XL`cC@G*prPSVVbklfp3yKyg$
z?eQX0`OswMG`rlDmUFVU6yKx{6@5{TctW_#jCy+I1uZwe>xpKkGU9K=n(`=HJ*7M{
zlSt1zysMwG-!Z6|ouxZgTY$;on%(t)xNmAzzQV(_wStuE76H;v8~O*Nu9Z6LIbAO{
zCv8$HF!$GeKAkWNLNyPK$~4Ib1KSly_nH{S
zZCA%JEZIbRrZ-Vcv*4itRjDPp{1bJm$+b?NotM1-Ij1N(@;!9>P_SbGBl3%3*zx|H
z)r|h(REO<#$ore09+|#u-a&t7I`PlincVuuf35eX-
}|u*tp2}aFS~Hv4gN0!oLD)2A}<`w({B8r
z17`DI@^N?1+ReV-`#PQeEISTnh?7oVFTO0=xnv`=g}Ip;zbYkqVs^MB{5@C%6J^aY
zS(CIcc6{>rPIYQSWW1k?u4rUCXRKAC1-WHRyNAUeyf@+;J!q!pU+^^6CJMrVb=CSz8XYc$0Fhssmky
zNlYCo%zALlV&I1L9QAkK?^`}QcdtAiSRs2
zJf1(n@l??Bx|_W029!gJrfz=YJ2FG1wttCjpN)Dequ)1RI&r3cO!w;#
z*U66o@a8|qXLnE65{g*6+&9?Eb5xO(b#YY{fu21x==
z(_kRQ4-AdQI?$E~pn0%(>JKhI1Tf2_xxCT+*rHYc@O9BU5_$D-jF7mu`>^LadgR3-i1w~Y{RpKilNW!&QnO1|
zguVIg?o8>;43o(QPvI!EW8tAAs