打包构建
This commit is contained in:
1 parent
a6e45e0cb1
commit
ee8772c1b7
36 files changed
+940
-21
No files matched your search
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"defaults": {
|
||||
"run-as": "package"
|
||||
},
|
||||
"username": "docker-pichost",
|
||||
"groupname": "docker-pichost"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"docker-project": {
|
||||
"projects": [
|
||||
{
|
||||
"name": "pichost",
|
||||
"path": "docker"
|
||||
}
|
||||
]
|
||||
},
|
||||
"data-share": {
|
||||
"shares": [
|
||||
{
|
||||
"name": "pichost/data",
|
||||
"permission": {
|
||||
"rw": [
|
||||
"docker-pichost"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
services:
|
||||
pichost:
|
||||
image: yeomaiyelove/pichost:1.5.1
|
||||
pull_policy: never
|
||||
container_name: pichost
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${wizard_port}:6892"
|
||||
volumes:
|
||||
- "/${TRIM_APPDEST_VOL}/@appshare/pichost/data:/data"
|
||||
- "${TRIM_APPDEST}/docker/start.sh:/fnos-start.sh:ro"
|
||||
entrypoint: ["/bin/sh", "/fnos-start.sh"]
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
# 飞牛 compose 入口:安装向导写入配置后,启动官方镜像前执行初始化或备份恢复。
|
||||
if [ -f /data/.fnos-restore.pending ]; then
|
||||
ARCHIVE="$(tr -d '\r\n' < /data/.fnos-restore.pending)"
|
||||
if [ -n "${ARCHIVE}" ] && [ -f "${ARCHIVE}" ]; then
|
||||
if node /app/server/cli/backup-restore.mjs "${ARCHIVE}" --overwrite; then
|
||||
rm -f /data/.fnos-restore.pending /data/.fnos-restore.failed
|
||||
else
|
||||
printf 'restore failed: %s\n' "${ARCHIVE}" > /data/.fnos-restore.failed
|
||||
rm -f /data/.fnos-restore.pending
|
||||
fi
|
||||
fi
|
||||
elif [ -f /data/fnos-bootstrap.mjs ]; then
|
||||
node /data/fnos-bootstrap.mjs || true
|
||||
fi
|
||||
exec /docker-entrypoint.sh
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
".url": {
|
||||
"pichost.main": {
|
||||
"title": "PicHost",
|
||||
"icon": "images/icon_{0}.png",
|
||||
"type": "url",
|
||||
"protocol": "http",
|
||||
"port": "${wizard_port}",
|
||||
"url": "/",
|
||||
"allUsers": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
exit 0
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
exit 0
|
||||
@@ -0,0 +1,148 @@
|
||||
import { existsSync, mkdirSync, readFileSync, renameSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { randomBytes, scryptSync } from 'node:crypto'
|
||||
import { DatabaseSync } from 'node:sqlite'
|
||||
|
||||
const USERNAME_PATTERN = /^[a-zA-Z0-9_]{3,32}$/
|
||||
const MIN_PASSWORD_LENGTH = 8
|
||||
const SETTINGS_ALLOW_REGISTRATION = 'allow_registration'
|
||||
|
||||
const dataDir = process.env.DATA_DIR || '/data'
|
||||
const configPath = join(dataDir, '.fnos-setup.json')
|
||||
const donePath = join(dataDir, '.fnos-setup.done')
|
||||
|
||||
function log(message) {
|
||||
console.log(`[PicHost fnos-bootstrap] ${message}`)
|
||||
}
|
||||
|
||||
function fail(message) {
|
||||
console.error(`[PicHost fnos-bootstrap] ${message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function hashPassword(password) {
|
||||
const salt = randomBytes(16).toString('hex')
|
||||
const derived = scryptSync(password, salt, 64, { N: 16384, r: 8, p: 1 })
|
||||
return `scrypt$16384$8$1$${salt}$${derived.toString('hex')}`
|
||||
}
|
||||
|
||||
// 与 server/utils/db.ts 初始建表一致;完整迁移由应用启动后的 getDb() 负责。
|
||||
function initDb(database) {
|
||||
database.exec(`
|
||||
PRAGMA journal_mode = WAL;
|
||||
CREATE TABLE IF NOT EXISTS activity_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
action TEXT NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
original_name TEXT NOT NULL,
|
||||
size INTEGER NOT NULL DEFAULT 0,
|
||||
content_type TEXT NOT NULL DEFAULT '',
|
||||
source TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_activity_logs_created_at
|
||||
ON activity_logs(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_activity_logs_action_created
|
||||
ON activity_logs(action, created_at DESC);
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL CHECK(role IN ('admin', 'user')),
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
token_hash TEXT NOT NULL,
|
||||
expires_at TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
|
||||
`)
|
||||
|
||||
const columns = database.prepare('PRAGMA table_info(users)').all()
|
||||
if (!columns.some(column => column.name === 'api_token')) {
|
||||
database.exec('ALTER TABLE users ADD COLUMN api_token TEXT')
|
||||
database.exec(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS idx_users_api_token ON users(api_token) WHERE api_token IS NOT NULL'
|
||||
)
|
||||
}
|
||||
if (!columns.some(column => column.name === 'auto_delete_days')) {
|
||||
database.exec('ALTER TABLE users ADD COLUMN auto_delete_days INTEGER NOT NULL DEFAULT 0')
|
||||
}
|
||||
const userColumns = database.prepare('PRAGMA table_info(users)').all()
|
||||
if (!userColumns.some(column => column.name === 'auto_delete_enabled_at')) {
|
||||
database.exec('ALTER TABLE users ADD COLUMN auto_delete_enabled_at TEXT')
|
||||
}
|
||||
}
|
||||
|
||||
function countUsers(database) {
|
||||
const row = database.prepare('SELECT COUNT(*) AS count FROM users').get()
|
||||
return Number(row?.count ?? 0)
|
||||
}
|
||||
|
||||
function setSetting(database, key, value) {
|
||||
const updatedAt = new Date().toISOString()
|
||||
database.prepare(`
|
||||
INSERT INTO settings (key, value, updated_at)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(key) DO UPDATE SET
|
||||
value = excluded.value,
|
||||
updated_at = excluded.updated_at
|
||||
`).run(key, value, updatedAt)
|
||||
}
|
||||
|
||||
mkdirSync(dataDir, { recursive: true })
|
||||
const dbPath = join(dataDir, 'pichost.db')
|
||||
const db = new DatabaseSync(dbPath)
|
||||
initDb(db)
|
||||
|
||||
if (countUsers(db) > 0) {
|
||||
if (existsSync(configPath)) {
|
||||
renameSync(configPath, donePath)
|
||||
}
|
||||
log('already initialized')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
log('no setup file, waiting for install wizard')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
let setup
|
||||
try {
|
||||
setup = JSON.parse(readFileSync(configPath, 'utf8'))
|
||||
} catch {
|
||||
fail('invalid .fnos-setup.json')
|
||||
}
|
||||
|
||||
const username = String(setup.username ?? '').trim()
|
||||
const password = String(setup.password ?? '')
|
||||
const allowRegistration = Boolean(setup.allowRegistration)
|
||||
|
||||
if (!USERNAME_PATTERN.test(username)) {
|
||||
fail('invalid admin username in setup file')
|
||||
}
|
||||
if (password.length < MIN_PASSWORD_LENGTH) {
|
||||
fail('invalid admin password in setup file')
|
||||
}
|
||||
|
||||
const createdAt = new Date().toISOString()
|
||||
db.prepare(`
|
||||
INSERT INTO users (username, password_hash, role, created_at)
|
||||
VALUES (?, ?, 'admin', ?)
|
||||
`).run(username, hashPassword(password), createdAt)
|
||||
|
||||
setSetting(db, SETTINGS_ALLOW_REGISTRATION, allowRegistration ? 'true' : 'false')
|
||||
db.close()
|
||||
|
||||
renameSync(configPath, donePath)
|
||||
log(`admin "${username}" created`)
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/bin/bash
|
||||
# 飞牛在此脚本阶段显示约 80%。只写文件并立刻返回,不要等待容器。
|
||||
DATA_DIR="/${TRIM_APPDEST_VOL}/@appshare/pichost/data"
|
||||
SETUP_FILE="${DATA_DIR}/.fnos-setup.json"
|
||||
RESTORE_PENDING="${DATA_DIR}/.fnos-restore.pending"
|
||||
BOOTSTRAP_SRC="$(cd "$(dirname "$0")" && pwd)/fnos-bootstrap.mjs"
|
||||
|
||||
SETUP_MODE="${wizard_setup_mode:-init}"
|
||||
USERNAME="${wizard_admin_username:-admin}"
|
||||
PASSWORD="${wizard_admin_password:-}"
|
||||
ALLOW_REGISTRATION="${wizard_allow_registration:-false}"
|
||||
BACKUP_PATH="${wizard_backup_path:-}"
|
||||
PORT="${wizard_port:-6892}"
|
||||
|
||||
log_fail() {
|
||||
echo "$1" > "${TRIM_TEMP_LOGFILE:-/dev/stderr}"
|
||||
}
|
||||
|
||||
case "${PORT}" in
|
||||
''|*[!0-9]*)
|
||||
log_fail "服务端口必须是数字。"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
if [ "${PORT}" -lt 1024 ] || [ "${PORT}" -gt 65535 ]; then
|
||||
log_fail "服务端口需在 1024–65535 之间。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${DATA_DIR}"
|
||||
|
||||
rm -f "${SETUP_FILE}" "${RESTORE_PENDING}" "${DATA_DIR}/.fnos-restore.failed" 2>/dev/null || true
|
||||
|
||||
case "${SETUP_MODE}" in
|
||||
restore)
|
||||
BACKUP_PATH="$(printf '%s' "${BACKUP_PATH}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||
if [ -z "${BACKUP_PATH}" ]; then
|
||||
log_fail "请填写备份文件的 NAS 绝对路径。"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "${BACKUP_PATH}" ]; then
|
||||
log_fail "找不到备份文件:${BACKUP_PATH}"
|
||||
exit 1
|
||||
fi
|
||||
case "${BACKUP_PATH}" in
|
||||
*.phost.tar.gz|*.tar.gz) ;;
|
||||
*)
|
||||
log_fail "备份文件须为 .phost.tar.gz 或 .tar.gz 格式。"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir -p "${DATA_DIR}/backups"
|
||||
RESTORE_ARCHIVE="${DATA_DIR}/backups/fnos-setup-restore.phost.tar.gz"
|
||||
cp "${BACKUP_PATH}" "${RESTORE_ARCHIVE}"
|
||||
printf '/data/backups/fnos-setup-restore.phost.tar.gz\n' > "${RESTORE_PENDING}"
|
||||
rm -f "${DATA_DIR}/fnos-bootstrap.mjs" 2>/dev/null || true
|
||||
;;
|
||||
init|*)
|
||||
if [ -z "${PASSWORD}" ]; then
|
||||
log_fail "请设置管理员密码。"
|
||||
exit 1
|
||||
fi
|
||||
case "${USERNAME}" in
|
||||
'')
|
||||
log_fail "请设置管理员用户名。"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
USERNAME="${USERNAME}" PASSWORD="${PASSWORD}" ALLOW_REGISTRATION="${ALLOW_REGISTRATION}" python3 - <<'PY' > "${SETUP_FILE}"
|
||||
import json, os
|
||||
print(json.dumps({
|
||||
"username": os.environ["USERNAME"],
|
||||
"password": os.environ["PASSWORD"],
|
||||
"allowRegistration": os.environ.get("ALLOW_REGISTRATION", "false").lower() == "true",
|
||||
}))
|
||||
PY
|
||||
else
|
||||
case "${ALLOW_REGISTRATION}" in
|
||||
true|TRUE|1|yes|on) ALLOW_JSON=true ;;
|
||||
*) ALLOW_JSON=false ;;
|
||||
esac
|
||||
printf '{"username":"%s","password":"%s","allowRegistration":%s}\n' \
|
||||
"${USERNAME}" "${PASSWORD}" "${ALLOW_JSON}" > "${SETUP_FILE}"
|
||||
fi
|
||||
|
||||
if [ -f "${BOOTSTRAP_SRC}" ]; then
|
||||
cp "${BOOTSTRAP_SRC}" "${DATA_DIR}/fnos-bootstrap.mjs"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
docker restart -t 2 pichost >/dev/null 2>&1 || true
|
||||
exit 0
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
DATA_DIR="/${TRIM_APPDEST_VOL}/@appshare/pichost/data"
|
||||
mkdir -p "${DATA_DIR}" 2>/dev/null || true
|
||||
exit 0
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
CONTAINER_NAME="pichost"
|
||||
|
||||
is_running() {
|
||||
docker inspect "$CONTAINER_NAME" 2>/dev/null | grep -q '"Status": "running"'
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
exit 0
|
||||
;;
|
||||
stop)
|
||||
exit 0
|
||||
;;
|
||||
status)
|
||||
if is_running; then
|
||||
exit 0
|
||||
fi
|
||||
exit 3
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: $1" > "${TRIM_TEMP_LOGFILE:-/dev/stderr}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
ACTION="${wizard_data_action:-keep}"
|
||||
|
||||
if [ "$ACTION" = "delete" ]; then
|
||||
DATA_DIR="/${TRIM_APPDEST_VOL}/@appshare/pichost/data"
|
||||
if [ -d "${DATA_DIR}" ]; then
|
||||
rm -rf "${DATA_DIR:?}"/*
|
||||
fi
|
||||
if [ -n "${TRIM_PKGVAR:-}" ] && [ -d "$TRIM_PKGVAR" ]; then
|
||||
rm -rf "${TRIM_PKGVAR:?}"/*
|
||||
fi
|
||||
if [ -n "${TRIM_PKGETC:-}" ] && [ -d "$TRIM_PKGETC" ]; then
|
||||
rm -rf "${TRIM_PKGETC:?}"/*
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
exit 0
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
# 升级 fpk 后:使用飞牛中已导入的本地镜像重建容器(pichost/data 数据目录不变)。
|
||||
COMPOSE_DIR="${TRIM_APPDEST}/docker"
|
||||
COMPOSE_FILE="${COMPOSE_DIR}/docker-compose.yaml"
|
||||
CONTAINER_NAME="pichost"
|
||||
|
||||
log_fail() {
|
||||
echo "$1" > "${TRIM_TEMP_LOGFILE:-/dev/stderr}"
|
||||
}
|
||||
|
||||
if [ ! -f "${COMPOSE_FILE}" ]; then
|
||||
log_fail "找不到 docker-compose.yaml"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IMAGE="$(sed -n 's/^[[:space:]]*image:[[:space:]]*//p' "${COMPOSE_FILE}" | head -1 | tr -d ' \r')"
|
||||
if [ -z "${IMAGE}" ]; then
|
||||
log_fail "无法从 compose 解析镜像名"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 升级向导不含 port 字段,尽量沿用旧容器端口
|
||||
if [ -z "${wizard_port:-}" ] && docker inspect "${CONTAINER_NAME}" >/dev/null 2>&1; then
|
||||
wizard_port="$(docker inspect "${CONTAINER_NAME}" --format '{{with index .HostConfig.PortBindings "6892/tcp"}}{{with index . 0}}{{.HostPort}}{{end}}{{end}}' 2>/dev/null || true)"
|
||||
fi
|
||||
export wizard_port="${wizard_port:-6892}"
|
||||
|
||||
if ! docker compose version >/dev/null 2>&1; then
|
||||
if docker inspect "${CONTAINER_NAME}" >/dev/null 2>&1; then
|
||||
docker restart -t 10 "${CONTAINER_NAME}" >/dev/null 2>&1 || {
|
||||
log_fail "docker compose 不可用,且容器重启失败"
|
||||
exit 1
|
||||
}
|
||||
exit 0
|
||||
fi
|
||||
log_fail "docker compose 不可用,且未找到 ${CONTAINER_NAME} 容器"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! (cd "${COMPOSE_DIR}" && docker compose up -d --force-recreate); then
|
||||
log_fail "重建容器失败,请检查 Docker 日志(镜像:${IMAGE})"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
exit 0
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"defaults": {
|
||||
"run-as": "package"
|
||||
},
|
||||
"username": "docker-pichost",
|
||||
"groupname": "docker-pichost"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"docker-project": {
|
||||
"projects": [
|
||||
{
|
||||
"name": "pichost",
|
||||
"path": "docker"
|
||||
}
|
||||
]
|
||||
},
|
||||
"data-share": {
|
||||
"shares": [
|
||||
{
|
||||
"name": "pichost/data",
|
||||
"permission": {
|
||||
"rw": [
|
||||
"docker-pichost"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
appname = pichost
|
||||
version = 1.5.1
|
||||
display_name = PicHost
|
||||
desc = 自托管图床:多用户与用户管理、彩色标签、密码重置、本地/对象存储、整站备份与跨后端同步、双域名分离、WebP 压缩、API/Twikoo,安装向导创建管理员
|
||||
source = thirdparty
|
||||
platform = x86
|
||||
maintainer = Muxui
|
||||
maintainer_url = https://muxui.com/posts/pichost-cloudflare-r2-image-host
|
||||
distributor = Muxui
|
||||
distributor_url = https://github.com/O96u/PicHost
|
||||
os_min_version = 0.9.27
|
||||
service_port = 6892
|
||||
checkport = true
|
||||
ctl_stop = true
|
||||
desktop_uidir = ui
|
||||
desktop_applaunchname = pichost.main
|
||||
install_type =
|
||||
checksum = 57f241c05731c1c9f84467f68d25f0c1
|
||||
@@ -0,0 +1,123 @@
|
||||
[
|
||||
{
|
||||
"stepTitle": "安装方式",
|
||||
"items": [
|
||||
{
|
||||
"type": "tips",
|
||||
"helpText": "安装前请先在飞牛 Docker 中导入 <strong>yeomaiyelove/pichost:1.5.1</strong> 镜像。PicHost 使用本地镜像,不会自动联网拉取。与 Web 首次设置一致,可选 <strong>创建管理员</strong> 或 <strong>从备份恢复</strong>(<code>.phost.tar.gz</code>)。图片与数据库保存在存储空间 <strong>pichost/data</strong> 共享目录。"
|
||||
},
|
||||
{
|
||||
"type": "radio",
|
||||
"field": "wizard_setup_mode",
|
||||
"label": "安装方式",
|
||||
"initValue": "init",
|
||||
"options": [
|
||||
{
|
||||
"label": "创建管理员(新装)",
|
||||
"value": "init"
|
||||
},
|
||||
{
|
||||
"label": "从备份恢复(换机 / 迁移)",
|
||||
"value": "restore"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"required": true,
|
||||
"message": "请选择安装方式"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"field": "wizard_port",
|
||||
"label": "服务端口",
|
||||
"initValue": "6892",
|
||||
"helpText": "PicHost 在 NAS 上对外访问的端口,默认 6892。请避免与系统或其他应用冲突。",
|
||||
"rules": [
|
||||
{
|
||||
"required": true,
|
||||
"message": "请输入端口号"
|
||||
},
|
||||
{
|
||||
"pattern": "^[0-9]+$",
|
||||
"message": "端口号必须是数字"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"stepTitle": "创建管理员",
|
||||
"items": [
|
||||
{
|
||||
"type": "tips",
|
||||
"helpText": "仅在选择 <strong>创建管理员</strong> 时需要填写。安装完成后点击桌面图标即可登录,无需再访问 /setup。"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"field": "wizard_admin_username",
|
||||
"label": "管理员用户名",
|
||||
"initValue": "admin",
|
||||
"rules": [
|
||||
{
|
||||
"pattern": "^[a-zA-Z0-9_]{3,32}$",
|
||||
"message": "用户名为 3–32 位字母、数字或下划线"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "password",
|
||||
"field": "wizard_admin_password",
|
||||
"label": "管理员密码",
|
||||
"initValue": "",
|
||||
"rules": [
|
||||
{
|
||||
"min": 8,
|
||||
"message": "密码至少 8 位"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "switch",
|
||||
"field": "wizard_allow_registration",
|
||||
"label": "允许用户注册",
|
||||
"initValue": "false",
|
||||
"helpText": "开启后,访客可在登录页自行注册普通用户账号;关闭则仅管理员可创建用户。"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"stepTitle": "从备份恢复",
|
||||
"items": [
|
||||
{
|
||||
"type": "tips",
|
||||
"helpText": "仅在选择 <strong>从备份恢复</strong> 时需要填写。请先把 <code>.phost.tar.gz</code> 备份包放到 NAS 上可访问的路径(如共享文件夹),再填写<strong>绝对路径</strong>。恢复完成后请使用<strong>备份包内原有的管理员账号</strong>登录;对象存储密钥需在恢复后重新填写。"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"field": "wizard_backup_path",
|
||||
"label": "备份文件路径",
|
||||
"initValue": "",
|
||||
"helpText": "示例:<code>/vol1/1000/backups/pichost.phost.tar.gz</code>(路径因 NAS 存储池而异,请按实际填写)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"stepTitle": "访问与反向代理(可选)",
|
||||
"items": [
|
||||
{
|
||||
"type": "tips",
|
||||
"helpText": "安装后可通过 <strong>http://<NAS IP>:<服务端口></strong> 或桌面图标进入后台。若使用公网域名,建议在飞牛 <strong>Lucky</strong> 中配置反向代理:前端填域名,后端填 <strong>http://<NAS 内网 IP>:<服务端口></strong>(如 <code>http://192.168.8.3:6892</code>)。"
|
||||
},
|
||||
{
|
||||
"type": "tips",
|
||||
"helpText": "若希望<strong>管理后台与图片直链使用不同域名</strong>,安装完成后在 <strong>设置 → 域名与链接</strong> 中启用「后台与图片域名分离」,并在 Lucky 为两个域名各建一条反向代理,<strong>后端地址相同</strong>即可。详见 <a href=\"https://o96u.github.io/PicHost/guide/domain-separation.html\" target=\"_blank\">双域名分离文档</a>。"
|
||||
},
|
||||
{
|
||||
"type": "tips",
|
||||
"helpText": "日常整站备份/跨后端同步在 <strong>存储 → 备份与迁移</strong>;也可通过 <code>docker exec pichost backup-export</code> 等 CLI 操作。详见 <a href=\"https://o96u.github.io/PicHost/guide/storage.html\" target=\"_blank\">存储文档</a>。"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
[
|
||||
{
|
||||
"stepTitle": "卸载 PicHost",
|
||||
"items": [
|
||||
{
|
||||
"type": "tips",
|
||||
"helpText": "卸载将停止 PicHost 服务。可选择是否删除图片与数据库。"
|
||||
},
|
||||
{
|
||||
"type": "radio",
|
||||
"field": "wizard_data_action",
|
||||
"label": "数据",
|
||||
"initValue": "keep",
|
||||
"options": [
|
||||
{
|
||||
"label": "保留数据(可重装后继续使用)",
|
||||
"value": "keep"
|
||||
},
|
||||
{
|
||||
"label": "删除所有数据",
|
||||
"value": "delete"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"required": true,
|
||||
"message": "请选择是否删除数据"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
Reference in new issue
Block a user