ci / ci (22, ubuntu-latest) (push) Canceled after 0s
Publish Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Canceled after 0s
Publish Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Canceled after 0s
Publish Docker image / merge (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
Consolidate local images under data/images/, add docker migrate CLI, startup index sync with orphan cleanup, and remove upload folder UI/API.
273 lines
11 KiB
Markdown
273 lines
11 KiB
Markdown
<p align="center">
|
||
<img src="app/assets/image/logo-light.png" alt="PicHost" width="96" />
|
||
</p>
|
||
|
||
<h1 align="center">PicHost</h1>
|
||
|
||
<p align="center"><strong>Lightweight personal image hosting</strong> · Simple uploads, clean management</p>
|
||
|
||
<p align="center">Self-hosted / Multi-user / Docker / API / Twikoo · Local disk or object storage</p>
|
||
|
||
<p align="center">
|
||
<a href="https://github.com/O96u/PicHost/blob/main/package.json"><img src="https://img.shields.io/github/package-json/v/O96u/PicHost?style=flat-square&color=22c55e" alt="version" /></a>
|
||
<a href="https://github.com/O96u/PicHost/actions/workflows/ci.yml"><img src="https://github.com/O96u/PicHost/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
|
||
<img src="https://img.shields.io/badge/Nuxt-4-00DC82?style=flat-square&logo=nuxt.js&logoColor=white" alt="Nuxt 4" />
|
||
<a href="https://hub.docker.com/r/muxui/pichost"><img src="https://img.shields.io/badge/Docker-muxui%2Fpichost-2496ED?style=flat-square&logo=docker&logoColor=white" alt="Docker" /></a>
|
||
<img src="https://img.shields.io/badge/license-GPL--3.0-blue?style=flat-square" alt="GPL-3.0" />
|
||
</p>
|
||
|
||
<p align="center">
|
||
<a href="#branches">Branches</a> ·
|
||
<a href="#quick-start">Quick Start</a> ·
|
||
<a href="#screenshots">Screenshots</a> ·
|
||
<a href="#features">Features</a> ·
|
||
<a href="#tech-stack">Tech Stack</a> ·
|
||
<a href="#roadmap">Roadmap</a> ·
|
||
<a href="README.md">简体中文</a>
|
||
</p>
|
||
|
||
---
|
||
|
||
## 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 |
|
||
| :------------------------------------: | :--------------------------------: |
|
||
|  |  |
|
||
|
||
| Stats & gallery | 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
|
||
|
||
## Quick Start
|
||
|
||
### Docker (recommended)
|
||
|
||
```bash
|
||
docker run -d \
|
||
--name pichost \
|
||
-p 6892:6892 \
|
||
-v ./data:/data \
|
||
--restart unless-stopped \
|
||
muxui/pichost:latest
|
||
```
|
||
|
||
Default port: **6892**. Open `http://<host>:6892` in your browser and complete the setup wizard.
|
||
|
||
**Forgot password** (requires `docker exec` / server access):
|
||
|
||
```bash
|
||
docker exec pichost reset-password # admin only, when there is exactly one
|
||
docker exec pichost reset-password <username> # admin or regular user
|
||
```
|
||
|
||
A random password is printed. Fails if the user does not exist.
|
||
|
||
Local: `npm run reset-password` or `npm run reset-password -- <username>`
|
||
|
||
### Upgrading to v1.2.0 (legacy local directories)
|
||
|
||
From v1.2.0, all images live under `data/images/`. Migration is two steps:
|
||
|
||
1. **CLI** (manual): move images from every top-level folder under `data/` except `images` into `data/images/{original-dir}/...`
|
||
2. **Startup sync** (automatic): scan disk, fill SQLite index, normalize legacy keys, remove orphan rows
|
||
|
||
```bash
|
||
docker exec pichost migrate # preview + data/mapping.json
|
||
docker exec pichost migrate --apply # move files
|
||
# upgrade image and restart — index sync logs appear on startup
|
||
```
|
||
|
||
Local: `npm run migrate` / `npm run migrate -- --apply`.
|
||
|
||
Full guide (layout, logs, URLs, **other image hosts**): [`docs/migration-to-v1.2.en.md`](docs/migration-to-v1.2.en.md)
|
||
|
||
Skip the CLI if you only use object storage or files are already under `data/images/`.
|
||
|
||
If you cloned the repo, you can also use `docker compose up -d` (see `docker-compose.yml`).
|
||
|
||
### Local development
|
||
|
||
```bash
|
||
npm install
|
||
cp .env.example .env # optional
|
||
npm run dev
|
||
```
|
||
|
||
Open `http://localhost:3000/setup` to create the admin account. Data defaults to `./data`.
|
||
|
||
## Tech Stack
|
||
|
||
| Layer | Technology |
|
||
| -------- | --------------------------------------------------- |
|
||
| Frontend | Nuxt 4, Vue 3, Nuxt UI 4, Tailwind CSS 4 |
|
||
| Backend | Nitro (Node.js), SQLite |
|
||
| Images | sharp (WebP transcoding & compression) |
|
||
| Auth | Session cookie, scrypt password hashing, API tokens |
|
||
| Storage | Local disk + S3-compatible backends (R2 / COS / OSS / AWS); `images` index in SQLite |
|
||
| Deploy | Docker (amd64 / arm64), docker compose |
|
||
| Tests | Vitest |
|
||
|
||
## Users & permissions
|
||
|
||
| Scenario | Auth | Ownership / visibility |
|
||
| ------------------------------ | -------------------- | --------------------------- |
|
||
| Web upload | Session | Current user |
|
||
| API upload + **user token** | `Auth-Token` | Token owner |
|
||
| API upload + **global token** | `Auth-Token` | Admin (`userId` null) |
|
||
| Twikoo `/api/index.php` | form `token` | Same as global token |
|
||
| Gallery list / search / delete | Session or token | Users: own only; admin: all |
|
||
| Direct link `GET /images/...` | None (Referer rules) | Public if URL is known |
|
||
|
||
All uploads are stored under `images/`.
|
||
|
||
## Storage layout
|
||
|
||
```
|
||
/data
|
||
├── pichost.db # SQLite: users, sessions, settings, storage_backends, images index
|
||
└── images/ # All images (local backend); may include migrated subpaths
|
||
```
|
||
|
||
New uploads: `images/randomId.webp` or `images/YYYY/MM/randomId.webp`. Migrated legacy paths may look like `images/blog/...`. Metadata lives in SQLite `images`. With cloud backends, blobs live in the bucket. Back up `data/images/`, `pichost.db`, and bucket data.
|
||
|
||
Upgrading from v1.1.x with parallel folders such as `data/blog/`: see [Upgrading to v1.2.0](#upgrading-to-v120-legacy-local-directories) or [`docs/migration-to-v1.2.en.md`](docs/migration-to-v1.2.en.md).
|
||
|
||
## API overview
|
||
|
||
| Method | Path | Description |
|
||
| ------ | -------------------- | ---------------------- |
|
||
| POST | `/api/images/upload` | Upload image |
|
||
| GET | `/api/images` | Paginated list |
|
||
| DELETE | `/api/images` | Delete one (`key`) |
|
||
| POST | `/api/index.php` | Twikoo / EasyImage 2.0 |
|
||
|
||
Upload example:
|
||
|
||
```bash
|
||
curl -X POST "https://pic.example.com/api/images/upload" \
|
||
-H "Auth-Token: YOUR_TOKEN" \
|
||
-F "image=@./demo.png"
|
||
```
|
||
|
||
Full docs and copyable cURL snippets are on the **API** page after deployment.
|
||
|
||
## Twikoo
|
||
|
||
| Setting | Value |
|
||
| ----------------- | ----------------------------------- |
|
||
| `IMAGE_CDN` | `easyimage` |
|
||
| `IMAGE_CDN_URL` | `https://your-domain/api/index.php` |
|
||
| `IMAGE_CDN_TOKEN` | Same as `API_UPLOAD_TOKEN` |
|
||
|
||
## Reverse proxy
|
||
|
||
- Proxy to port **6892**; HTTPS recommended
|
||
- Nginx: `client_max_body_size` ≥ 12m
|
||
- Set `IMAGE_BASE_URL` (single domain) or `SITE_BASE_URL` + `IMAGE_BASE_URL` (dual-domain separation)
|
||
|
||
### Recommended architecture (dual domains)
|
||
|
||
**One Docker instance + two domains**, both fully proxied to `6892` (isolation via PicHost middleware):
|
||
|
||
```nginx
|
||
# admin.example.com — admin + API + images
|
||
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 — same full proxy; PicHost middleware blocks non-image paths
|
||
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;
|
||
}
|
||
}
|
||
```
|
||
|
||
FN NAS [Lucky](https://github.com/gdy666/lucky) reverse proxy (public hostname → backend `http://<LAN-IP>:6892`):
|
||
|
||
| Site domain (admin) | Image domain |
|
||
| :-----------------: | :----------: |
|
||
|  |  |
|
||
|
||
See **[docs/domain-separation.en.md](docs/domain-separation.en.md)** for Caddy/NPM notes, env vars, and setup fields.
|
||
|
||
## Development
|
||
|
||
```bash
|
||
npm run dev # Dev server
|
||
npm run build # Production build
|
||
npm run start # Run .output
|
||
npm run lint # ESLint
|
||
npm run typecheck # Type check
|
||
npm test # Unit tests
|
||
npm run reset-password # Reset password (optional username)
|
||
npm run migrate # Preview legacy dir migration (-- --apply to run)
|
||
```
|
||
|
||
## Roadmap
|
||
|
||
| Version | Plan |
|
||
| ---------- | -------------------------------------------------------------- |
|
||
| **v1.2.0** | Unified `images/` storage, CLI migration, startup index sync, upload source stats (in progress) |
|
||
| **v1.1.5** | Gallery preview reliability: auto-retry, storage verify, no-cache errors |
|
||
| **v1.1.4** | Flat vs date storage paths, basename public URLs, bidirectional domain isolation |
|
||
| **v1.1.3** | Dual-domain separation, optional `images/` URL prefix, update check |
|
||
| **v1.1.2** | Activity logs, gallery storage filter, upload rate limits |
|
||
| **v1.1.1** | Session check fixes, storage placeholder cleanup, Docker logging |
|
||
| **v1.1.0** | Multi-backend object storage, hybrid URLs, storage UI |
|
||
| **v1.0.4** | Password visibility toggle, legacy ADMIN_SECRET migration |
|
||
| **v1.0.3** | CI and Docker multi-arch build fixes |
|
||
| **v1.0.2** | i18n, mobile menus, stats/upload UI, Docker password reset |
|
||
| **v1.0.1** | Settings version display, mobile upload preferences layout fix |
|
||
| **v1.0.0** | Local storage, multi-user, API, Twikoo |
|
||
|
||
## License
|
||
|
||
[GPL-3.0](LICENSE)
|
||
|
||
## Friend links
|
||
|
||
[LINUX DO](https://linux.do/)
|