cloudisk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +17 -0
- package/README.md +120 -0
- package/dist/config/index.d.ts +27 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +45 -0
- package/dist/config/index.js.map +1 -0
- package/dist/dto/auth.dto.d.ts +14 -0
- package/dist/dto/auth.dto.d.ts.map +1 -0
- package/dist/dto/auth.dto.js +16 -0
- package/dist/dto/auth.dto.js.map +1 -0
- package/dist/dto/user.dto.d.ts +76 -0
- package/dist/dto/user.dto.d.ts.map +1 -0
- package/dist/dto/user.dto.js +70 -0
- package/dist/dto/user.dto.js.map +1 -0
- package/dist/handlers/auth.handler.d.ts +10 -0
- package/dist/handlers/auth.handler.d.ts.map +1 -0
- package/dist/handlers/auth.handler.js +41 -0
- package/dist/handlers/auth.handler.js.map +1 -0
- package/dist/handlers/disk.handler.d.ts +12 -0
- package/dist/handlers/disk.handler.d.ts.map +1 -0
- package/dist/handlers/disk.handler.js +61 -0
- package/dist/handlers/disk.handler.js.map +1 -0
- package/dist/handlers/samba.handler.d.ts +16 -0
- package/dist/handlers/samba.handler.d.ts.map +1 -0
- package/dist/handlers/samba.handler.js +120 -0
- package/dist/handlers/samba.handler.js.map +1 -0
- package/dist/handlers/share.handler.d.ts +11 -0
- package/dist/handlers/share.handler.d.ts.map +1 -0
- package/dist/handlers/share.handler.js +52 -0
- package/dist/handlers/share.handler.js.map +1 -0
- package/dist/handlers/system.handler.d.ts +17 -0
- package/dist/handlers/system.handler.d.ts.map +1 -0
- package/dist/handlers/system.handler.js +210 -0
- package/dist/handlers/system.handler.js.map +1 -0
- package/dist/handlers/user.handler.d.ts +16 -0
- package/dist/handlers/user.handler.d.ts.map +1 -0
- package/dist/handlers/user.handler.js +101 -0
- package/dist/handlers/user.handler.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +799 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/errors.d.ts +13 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +35 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/log-buffer.d.ts +20 -0
- package/dist/lib/log-buffer.d.ts.map +1 -0
- package/dist/lib/log-buffer.js +75 -0
- package/dist/lib/log-buffer.js.map +1 -0
- package/dist/lib/response.d.ts +5 -0
- package/dist/lib/response.d.ts.map +1 -0
- package/dist/lib/response.js +19 -0
- package/dist/lib/response.js.map +1 -0
- package/dist/lib/shell.d.ts +9 -0
- package/dist/lib/shell.d.ts.map +1 -0
- package/dist/lib/shell.js +32 -0
- package/dist/lib/shell.js.map +1 -0
- package/dist/middleware/auth.d.ts +17 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +43 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +3 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +14 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/logger.d.ts +3 -0
- package/dist/middleware/logger.d.ts.map +1 -0
- package/dist/middleware/logger.js +25 -0
- package/dist/middleware/logger.js.map +1 -0
- package/dist/middleware/validate.d.ts +3 -0
- package/dist/middleware/validate.d.ts.map +1 -0
- package/dist/middleware/validate.js +28 -0
- package/dist/middleware/validate.js.map +1 -0
- package/dist/operators/disk.operator.d.ts +29 -0
- package/dist/operators/disk.operator.d.ts.map +1 -0
- package/dist/operators/disk.operator.js +70 -0
- package/dist/operators/disk.operator.js.map +1 -0
- package/dist/operators/linux-user.operator.d.ts +13 -0
- package/dist/operators/linux-user.operator.d.ts.map +1 -0
- package/dist/operators/linux-user.operator.js +47 -0
- package/dist/operators/linux-user.operator.js.map +1 -0
- package/dist/operators/quota.operator.d.ts +18 -0
- package/dist/operators/quota.operator.d.ts.map +1 -0
- package/dist/operators/quota.operator.js +54 -0
- package/dist/operators/quota.operator.js.map +1 -0
- package/dist/operators/samba.operator.d.ts +41 -0
- package/dist/operators/samba.operator.d.ts.map +1 -0
- package/dist/operators/samba.operator.js +274 -0
- package/dist/operators/samba.operator.js.map +1 -0
- package/dist/operators/share.operator.d.ts +21 -0
- package/dist/operators/share.operator.d.ts.map +1 -0
- package/dist/operators/share.operator.js +67 -0
- package/dist/operators/share.operator.js.map +1 -0
- package/dist/repositories/admin.repo.d.ts +30 -0
- package/dist/repositories/admin.repo.d.ts.map +1 -0
- package/dist/repositories/admin.repo.js +20 -0
- package/dist/repositories/admin.repo.js.map +1 -0
- package/dist/repositories/user.repo.d.ts +44 -0
- package/dist/repositories/user.repo.d.ts.map +1 -0
- package/dist/repositories/user.repo.js +3 -0
- package/dist/repositories/user.repo.js.map +1 -0
- package/dist/repositories/user.repo.sqlite.d.ts +16 -0
- package/dist/repositories/user.repo.sqlite.d.ts.map +1 -0
- package/dist/repositories/user.repo.sqlite.js +47 -0
- package/dist/repositories/user.repo.sqlite.js.map +1 -0
- package/dist/routes/auth.routes.d.ts +4 -0
- package/dist/routes/auth.routes.d.ts.map +1 -0
- package/dist/routes/auth.routes.js +15 -0
- package/dist/routes/auth.routes.js.map +1 -0
- package/dist/routes/disk.routes.d.ts +4 -0
- package/dist/routes/disk.routes.d.ts.map +1 -0
- package/dist/routes/disk.routes.js +16 -0
- package/dist/routes/disk.routes.js.map +1 -0
- package/dist/routes/index.d.ts +4 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +64 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/samba.routes.d.ts +4 -0
- package/dist/routes/samba.routes.d.ts.map +1 -0
- package/dist/routes/samba.routes.js +20 -0
- package/dist/routes/samba.routes.js.map +1 -0
- package/dist/routes/share.routes.d.ts +4 -0
- package/dist/routes/share.routes.d.ts.map +1 -0
- package/dist/routes/share.routes.js +15 -0
- package/dist/routes/share.routes.js.map +1 -0
- package/dist/routes/system.routes.d.ts +4 -0
- package/dist/routes/system.routes.d.ts.map +1 -0
- package/dist/routes/system.routes.js +20 -0
- package/dist/routes/system.routes.js.map +1 -0
- package/dist/routes/user.routes.d.ts +5 -0
- package/dist/routes/user.routes.d.ts.map +1 -0
- package/dist/routes/user.routes.js +44 -0
- package/dist/routes/user.routes.js.map +1 -0
- package/dist/services/auth.service.d.ts +23 -0
- package/dist/services/auth.service.d.ts.map +1 -0
- package/dist/services/auth.service.js +62 -0
- package/dist/services/auth.service.js.map +1 -0
- package/dist/services/disk.service.d.ts +12 -0
- package/dist/services/disk.service.d.ts.map +1 -0
- package/dist/services/disk.service.js +34 -0
- package/dist/services/disk.service.js.map +1 -0
- package/dist/services/init.service.d.ts +8 -0
- package/dist/services/init.service.d.ts.map +1 -0
- package/dist/services/init.service.js +47 -0
- package/dist/services/init.service.js.map +1 -0
- package/dist/services/quota.service.d.ts +14 -0
- package/dist/services/quota.service.d.ts.map +1 -0
- package/dist/services/quota.service.js +32 -0
- package/dist/services/quota.service.js.map +1 -0
- package/dist/services/samba.service.d.ts +45 -0
- package/dist/services/samba.service.d.ts.map +1 -0
- package/dist/services/samba.service.js +61 -0
- package/dist/services/samba.service.js.map +1 -0
- package/dist/services/share.service.d.ts +18 -0
- package/dist/services/share.service.d.ts.map +1 -0
- package/dist/services/share.service.js +29 -0
- package/dist/services/share.service.js.map +1 -0
- package/dist/services/user.service.d.ts +48 -0
- package/dist/services/user.service.d.ts.map +1 -0
- package/dist/services/user.service.js +196 -0
- package/dist/services/user.service.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/service.interfaces.d.ts +45 -0
- package/dist/types/service.interfaces.d.ts.map +1 -0
- package/dist/types/service.interfaces.js +3 -0
- package/dist/types/service.interfaces.js.map +1 -0
- package/package.json +63 -0
- package/prisma/schema.prisma +53 -0
- package/public/assets/Dashboard-BupoWE_7.js +1 -0
- package/public/assets/DiskManage-BY6Sy7T6.js +1 -0
- package/public/assets/InitWizard-CA9epEkr.css +1 -0
- package/public/assets/InitWizard-D5Xj7dX0.js +1 -0
- package/public/assets/Layout-D-CPXnPP.js +1 -0
- package/public/assets/Layout-DaQY8YzE.css +1 -0
- package/public/assets/LogViewer-CIJZzoXw.css +1 -0
- package/public/assets/LogViewer-DDAIjW9J.js +1 -0
- package/public/assets/Login-D92i6dQA.js +1 -0
- package/public/assets/Login-DimFYijw.css +1 -0
- package/public/assets/ServiceManage-CIWLurL_.css +1 -0
- package/public/assets/ServiceManage-D-ZWP7Ry.js +1 -0
- package/public/assets/ShareManage-BMsFoGea.js +1 -0
- package/public/assets/UserList-CqeA7r2Y.js +1 -0
- package/public/assets/index-B-crAlzg.css +1 -0
- package/public/assets/index-CfC5GCM9.js +100 -0
- package/public/index.html +14 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const path_1 = __importDefault(require("path"));
|
|
42
|
+
const fs_1 = __importDefault(require("fs"));
|
|
43
|
+
const pkg = require('../package.json');
|
|
44
|
+
const ETC_DIR = '/etc/cloudisk';
|
|
45
|
+
const program = new commander_1.Command();
|
|
46
|
+
program
|
|
47
|
+
.name('cloudisk')
|
|
48
|
+
.description('Samba 文件共享管理平台')
|
|
49
|
+
.version(pkg.version);
|
|
50
|
+
program
|
|
51
|
+
.command('init')
|
|
52
|
+
.description('初始化配置和数据目录')
|
|
53
|
+
.action(() => {
|
|
54
|
+
// Create directories
|
|
55
|
+
const dirs = [ETC_DIR, `${ETC_DIR}/data`, `${ETC_DIR}/logs`];
|
|
56
|
+
for (const dir of dirs) {
|
|
57
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
58
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
59
|
+
console.log(` 创建目录: ${dir}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Create .env
|
|
63
|
+
const envFile = `${ETC_DIR}/.env`;
|
|
64
|
+
if (fs_1.default.existsSync(envFile)) {
|
|
65
|
+
console.log(`配置文件已存在: ${envFile}`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const template = `# 服务
|
|
69
|
+
PORT=9528
|
|
70
|
+
NODE_ENV=production
|
|
71
|
+
|
|
72
|
+
# 数据库
|
|
73
|
+
DATABASE_URL=file:${ETC_DIR}/data/cloud_storage.db
|
|
74
|
+
|
|
75
|
+
# 认证 (请修改为随机字符串)
|
|
76
|
+
JWT_SECRET=change-me-to-a-random-string
|
|
77
|
+
JWT_EXPIRES_IN=24h
|
|
78
|
+
|
|
79
|
+
# 系统
|
|
80
|
+
SHARE_BASE=/share
|
|
81
|
+
SHELL_TIMEOUT=30000
|
|
82
|
+
|
|
83
|
+
# 日志
|
|
84
|
+
LOG_LEVEL=info
|
|
85
|
+
`;
|
|
86
|
+
fs_1.default.writeFileSync(envFile, template);
|
|
87
|
+
console.log(`已生成配置文件: ${envFile}`);
|
|
88
|
+
console.log('请编辑配置后启动服务');
|
|
89
|
+
});
|
|
90
|
+
program
|
|
91
|
+
.command('migrate')
|
|
92
|
+
.description('从 account.json 迁移用户数据')
|
|
93
|
+
.option('-f, --file <path>', 'account.json 路径', './account.json')
|
|
94
|
+
.action(async (opts) => {
|
|
95
|
+
// Load env
|
|
96
|
+
const envFile = `${ETC_DIR}/.env`;
|
|
97
|
+
if (fs_1.default.existsSync(envFile)) {
|
|
98
|
+
require('dotenv').config({ path: envFile });
|
|
99
|
+
}
|
|
100
|
+
const { PrismaClient } = require('@prisma/client');
|
|
101
|
+
const bcrypt = require('bcryptjs');
|
|
102
|
+
const accountFile = path_1.default.resolve(opts.file);
|
|
103
|
+
if (!fs_1.default.existsSync(accountFile)) {
|
|
104
|
+
console.error(`文件不存在: ${accountFile}`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
const data = JSON.parse(fs_1.default.readFileSync(accountFile, 'utf-8'));
|
|
108
|
+
const accounts = data.accounts || [];
|
|
109
|
+
const prisma = new PrismaClient();
|
|
110
|
+
await prisma.$connect();
|
|
111
|
+
let created = 0;
|
|
112
|
+
let skipped = 0;
|
|
113
|
+
for (const account of accounts) {
|
|
114
|
+
const existing = await prisma.user.findUnique({ where: { username: account.user } });
|
|
115
|
+
if (existing) {
|
|
116
|
+
console.log(` 跳过: ${account.user} (已存在)`);
|
|
117
|
+
skipped++;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const hashed = await bcrypt.hash(account.password, 10);
|
|
121
|
+
await prisma.user.create({
|
|
122
|
+
data: {
|
|
123
|
+
username: account.user,
|
|
124
|
+
password: hashed,
|
|
125
|
+
groupName: account.user,
|
|
126
|
+
sharePath: `/share/${account.user}`,
|
|
127
|
+
quotaSoft: account.limit || 50,
|
|
128
|
+
quotaHard: account.limit || 50,
|
|
129
|
+
status: 'active',
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
console.log(` 已导入: ${account.user}`);
|
|
133
|
+
created++;
|
|
134
|
+
}
|
|
135
|
+
// Create default admin
|
|
136
|
+
const admin = await prisma.admin.findUnique({ where: { username: 'admin' } });
|
|
137
|
+
if (!admin) {
|
|
138
|
+
const hashed = await bcrypt.hash('admin123', 10);
|
|
139
|
+
await prisma.admin.create({
|
|
140
|
+
data: { username: 'admin', password: hashed, role: 'superadmin' },
|
|
141
|
+
});
|
|
142
|
+
console.log(' 已创建管理员: admin / admin123');
|
|
143
|
+
}
|
|
144
|
+
await prisma.$disconnect();
|
|
145
|
+
console.log(`\n迁移完成: 创建 ${created}, 跳过 ${skipped}`);
|
|
146
|
+
});
|
|
147
|
+
const adminCmd = program.command('admin').description('管理员相关命令');
|
|
148
|
+
adminCmd
|
|
149
|
+
.command('reset-password')
|
|
150
|
+
.description('重置管理员密码')
|
|
151
|
+
.requiredOption('-u, --username <username>', '管理员用户名', 'admin')
|
|
152
|
+
.requiredOption('-p, --password <password>', '新密码')
|
|
153
|
+
.action(async (opts) => {
|
|
154
|
+
const envFile = `${ETC_DIR}/.env`;
|
|
155
|
+
if (fs_1.default.existsSync(envFile)) {
|
|
156
|
+
require('dotenv').config({ path: envFile });
|
|
157
|
+
}
|
|
158
|
+
const { PrismaClient } = require('@prisma/client');
|
|
159
|
+
const bcrypt = require('bcryptjs');
|
|
160
|
+
const prisma = new PrismaClient();
|
|
161
|
+
await prisma.$connect();
|
|
162
|
+
try {
|
|
163
|
+
const admin = await prisma.admin.findUnique({ where: { username: opts.username } });
|
|
164
|
+
if (!admin) {
|
|
165
|
+
console.error(`管理员 '${opts.username}' 不存在`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
if (opts.password.length < 6) {
|
|
169
|
+
console.error('密码长度不能少于6位');
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
const hashed = await bcrypt.hash(opts.password, 10);
|
|
173
|
+
await prisma.admin.update({
|
|
174
|
+
where: { username: opts.username },
|
|
175
|
+
data: { password: hashed },
|
|
176
|
+
});
|
|
177
|
+
console.log(`管理员 '${opts.username}' 密码已重置`);
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
await prisma.$disconnect();
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
const userCmd = program.command('user').description('用户相关命令');
|
|
184
|
+
userCmd
|
|
185
|
+
.command('list')
|
|
186
|
+
.description('列出所有用户')
|
|
187
|
+
.option('-j, --json', '以 JSON 格式输出')
|
|
188
|
+
.action(async (opts) => {
|
|
189
|
+
const envFile = `${ETC_DIR}/.env`;
|
|
190
|
+
if (fs_1.default.existsSync(envFile)) {
|
|
191
|
+
require('dotenv').config({ path: envFile });
|
|
192
|
+
}
|
|
193
|
+
const { PrismaClient } = require('@prisma/client');
|
|
194
|
+
const prisma = new PrismaClient();
|
|
195
|
+
await prisma.$connect();
|
|
196
|
+
try {
|
|
197
|
+
const users = await prisma.user.findMany({
|
|
198
|
+
select: {
|
|
199
|
+
id: true,
|
|
200
|
+
username: true,
|
|
201
|
+
groupName: true,
|
|
202
|
+
sharePath: true,
|
|
203
|
+
quotaSoft: true,
|
|
204
|
+
quotaHard: true,
|
|
205
|
+
status: true,
|
|
206
|
+
createdAt: true,
|
|
207
|
+
},
|
|
208
|
+
orderBy: { id: 'asc' },
|
|
209
|
+
});
|
|
210
|
+
if (opts.json) {
|
|
211
|
+
console.log(JSON.stringify(users, null, 2));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (users.length === 0) {
|
|
215
|
+
console.log('暂无用户');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
// Table format
|
|
219
|
+
const header = ['ID', '用户名', '用户组', '共享路径', '配额(GB)', '状态', '创建时间'];
|
|
220
|
+
const rows = users.map((u) => [
|
|
221
|
+
String(u.id),
|
|
222
|
+
u.username,
|
|
223
|
+
u.groupName,
|
|
224
|
+
u.sharePath,
|
|
225
|
+
`${u.quotaSoft}/${u.quotaHard}`,
|
|
226
|
+
u.status,
|
|
227
|
+
new Date(u.createdAt).toLocaleString('zh-CN'),
|
|
228
|
+
]);
|
|
229
|
+
// Calculate column widths
|
|
230
|
+
const widths = header.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
231
|
+
const pad = (s, w) => s + ' '.repeat(w - s.length);
|
|
232
|
+
const sep = widths.map((w) => '-'.repeat(w)).join(' ');
|
|
233
|
+
console.log(header.map((h, i) => pad(h, widths[i])).join(' '));
|
|
234
|
+
console.log(sep);
|
|
235
|
+
for (const row of rows) {
|
|
236
|
+
console.log(row.map((s, i) => pad(s, widths[i])).join(' '));
|
|
237
|
+
}
|
|
238
|
+
console.log(`\n共 ${users.length} 个用户`);
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
await prisma.$disconnect();
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
userCmd
|
|
245
|
+
.command('add')
|
|
246
|
+
.description('添加用户')
|
|
247
|
+
.requiredOption('-u, --username <username>', '用户名')
|
|
248
|
+
.requiredOption('-p, --password <password>', '密码 (至少6位)')
|
|
249
|
+
.option('-q, --quota <gb>', '磁盘配额 (GB)', '50')
|
|
250
|
+
.action(async (opts) => {
|
|
251
|
+
const envFile = `${ETC_DIR}/.env`;
|
|
252
|
+
if (fs_1.default.existsSync(envFile)) {
|
|
253
|
+
require('dotenv').config({ path: envFile });
|
|
254
|
+
}
|
|
255
|
+
const { PrismaClient } = require('@prisma/client');
|
|
256
|
+
const bcrypt = require('bcryptjs');
|
|
257
|
+
const { execSync } = require('child_process');
|
|
258
|
+
if (opts.password.length < 6) {
|
|
259
|
+
console.error('密码长度不能少于6位');
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
const shareBase = process.env.SHARE_BASE || '/share';
|
|
263
|
+
const sharePath = `${shareBase}/${opts.username}`;
|
|
264
|
+
const quotaGB = parseInt(opts.quota);
|
|
265
|
+
const prisma = new PrismaClient();
|
|
266
|
+
await prisma.$connect();
|
|
267
|
+
try {
|
|
268
|
+
// Check if user exists in DB
|
|
269
|
+
const existing = await prisma.user.findUnique({ where: { username: opts.username } });
|
|
270
|
+
if (existing) {
|
|
271
|
+
console.error(`用户 '${opts.username}' 已存在`);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
// 1. Create system user
|
|
275
|
+
try {
|
|
276
|
+
execSync(`useradd -m ${opts.username}`, { stdio: 'pipe' });
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
if (!err.stderr?.toString().includes('already exists')) {
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// 2. Set password
|
|
284
|
+
execSync(`echo '${opts.username}:${opts.password}' | chpasswd`, { stdio: 'pipe' });
|
|
285
|
+
console.log(` 系统用户已创建: ${opts.username}`);
|
|
286
|
+
// 3. Create share directory
|
|
287
|
+
if (!fs_1.default.existsSync(sharePath)) {
|
|
288
|
+
fs_1.default.mkdirSync(sharePath, { recursive: true });
|
|
289
|
+
}
|
|
290
|
+
execSync(`chown -R ${opts.username}:${opts.username} ${sharePath}`, { stdio: 'pipe' });
|
|
291
|
+
execSync(`chmod 0755 ${sharePath}`, { stdio: 'pipe' });
|
|
292
|
+
console.log(` 共享目录已创建: ${sharePath}`);
|
|
293
|
+
// 4. Add Samba user
|
|
294
|
+
try {
|
|
295
|
+
execSync(`echo -e '${opts.password}\\n${opts.password}' | smbpasswd -a -s ${opts.username}`, { stdio: 'pipe' });
|
|
296
|
+
console.log(` Samba 用户已添加`);
|
|
297
|
+
// Write Samba share config
|
|
298
|
+
const confPath = '/etc/samba/smb.conf';
|
|
299
|
+
const shareConfig = `
|
|
300
|
+
[${opts.username}_share]
|
|
301
|
+
\tpath = ${sharePath}
|
|
302
|
+
\tbrowseable = No
|
|
303
|
+
\twrite list = ${opts.username}
|
|
304
|
+
\tvalid users = ${opts.username}
|
|
305
|
+
`;
|
|
306
|
+
fs_1.default.appendFileSync(confPath, shareConfig);
|
|
307
|
+
console.log(` Samba 共享配置已写入`);
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
console.log(` Samba 配置跳过 (Samba 未安装)`);
|
|
311
|
+
}
|
|
312
|
+
// 5. Set quota
|
|
313
|
+
try {
|
|
314
|
+
if (fs_1.default.existsSync(shareBase)) {
|
|
315
|
+
execSync(`xfs_quota -x -c "limit -u bsoft=${quotaGB}g bhard=${quotaGB}g ${opts.username}" ${shareBase}`, { stdio: 'pipe' });
|
|
316
|
+
console.log(` 磁盘配额已设置: ${quotaGB}GB`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
console.log(` 配额设置跳过 (非 XFS 文件系统)`);
|
|
321
|
+
}
|
|
322
|
+
// 6. Create DB record
|
|
323
|
+
const hashed = await bcrypt.hash(opts.password, 10);
|
|
324
|
+
await prisma.user.create({
|
|
325
|
+
data: {
|
|
326
|
+
username: opts.username,
|
|
327
|
+
password: hashed,
|
|
328
|
+
groupName: opts.username,
|
|
329
|
+
sharePath,
|
|
330
|
+
quotaSoft: quotaGB,
|
|
331
|
+
quotaHard: quotaGB,
|
|
332
|
+
status: 'active',
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
console.log(`\n用户 '${opts.username}' 创建成功`);
|
|
336
|
+
}
|
|
337
|
+
catch (err) {
|
|
338
|
+
console.error(`创建用户失败: ${err.message}`);
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
finally {
|
|
342
|
+
await prisma.$disconnect();
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
userCmd
|
|
346
|
+
.command('delete')
|
|
347
|
+
.description('删除用户')
|
|
348
|
+
.requiredOption('-u, --username <username>', '用户名')
|
|
349
|
+
.option('--force', '跳过确认提示')
|
|
350
|
+
.action(async (opts) => {
|
|
351
|
+
const envFile = `${ETC_DIR}/.env`;
|
|
352
|
+
if (fs_1.default.existsSync(envFile)) {
|
|
353
|
+
require('dotenv').config({ path: envFile });
|
|
354
|
+
}
|
|
355
|
+
const { PrismaClient } = require('@prisma/client');
|
|
356
|
+
const { execSync } = require('child_process');
|
|
357
|
+
const prisma = new PrismaClient();
|
|
358
|
+
await prisma.$connect();
|
|
359
|
+
try {
|
|
360
|
+
const user = await prisma.user.findUnique({ where: { username: opts.username } });
|
|
361
|
+
if (!user) {
|
|
362
|
+
console.error(`用户 '${opts.username}' 不存在`);
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
if (!opts.force) {
|
|
366
|
+
const readline = require('readline');
|
|
367
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
368
|
+
const answer = await new Promise((resolve) => {
|
|
369
|
+
rl.question(`确认删除用户 '${opts.username}' 及其所有数据?(y/N): `, resolve);
|
|
370
|
+
});
|
|
371
|
+
rl.close();
|
|
372
|
+
if (answer.toLowerCase() !== 'y') {
|
|
373
|
+
console.log('已取消');
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// 1. Remove quota
|
|
378
|
+
try {
|
|
379
|
+
const shareBase = process.env.SHARE_BASE || '/share';
|
|
380
|
+
execSync(`xfs_quota -x -c "limit -u bsoft=0 bhard=0 ${opts.username}" ${shareBase}`, { stdio: 'pipe' });
|
|
381
|
+
}
|
|
382
|
+
catch { }
|
|
383
|
+
// 2. Remove Samba user and config
|
|
384
|
+
try {
|
|
385
|
+
execSync(`smbpasswd -x ${opts.username}`, { stdio: 'pipe' });
|
|
386
|
+
}
|
|
387
|
+
catch { }
|
|
388
|
+
try {
|
|
389
|
+
const confPath = '/etc/samba/smb.conf';
|
|
390
|
+
if (fs_1.default.existsSync(confPath)) {
|
|
391
|
+
let content = fs_1.default.readFileSync(confPath, 'utf-8');
|
|
392
|
+
const regex = new RegExp(`\\[${opts.username}[^\\]]*\\][^\\[]*`, 'g');
|
|
393
|
+
content = content.replace(regex, '');
|
|
394
|
+
fs_1.default.writeFileSync(confPath, content);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch { }
|
|
398
|
+
// 3. Delete system user
|
|
399
|
+
try {
|
|
400
|
+
execSync(`userdel -r ${opts.username}`, { stdio: 'pipe' });
|
|
401
|
+
}
|
|
402
|
+
catch { }
|
|
403
|
+
// 4. Delete DB record
|
|
404
|
+
await prisma.user.delete({ where: { id: user.id } });
|
|
405
|
+
console.log(`用户 '${opts.username}' 已删除`);
|
|
406
|
+
}
|
|
407
|
+
catch (err) {
|
|
408
|
+
console.error(`删除用户失败: ${err.message}`);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
finally {
|
|
412
|
+
await prisma.$disconnect();
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
userCmd
|
|
416
|
+
.command('change-password')
|
|
417
|
+
.description('修改用户密码')
|
|
418
|
+
.requiredOption('-u, --username <username>', '用户名')
|
|
419
|
+
.requiredOption('-p, --password <password>', '新密码 (至少6位)')
|
|
420
|
+
.action(async (opts) => {
|
|
421
|
+
const envFile = `${ETC_DIR}/.env`;
|
|
422
|
+
if (fs_1.default.existsSync(envFile)) {
|
|
423
|
+
require('dotenv').config({ path: envFile });
|
|
424
|
+
}
|
|
425
|
+
const { PrismaClient } = require('@prisma/client');
|
|
426
|
+
const bcrypt = require('bcryptjs');
|
|
427
|
+
const { execSync } = require('child_process');
|
|
428
|
+
if (opts.password.length < 6) {
|
|
429
|
+
console.error('密码长度不能少于6位');
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
const prisma = new PrismaClient();
|
|
433
|
+
await prisma.$connect();
|
|
434
|
+
try {
|
|
435
|
+
const user = await prisma.user.findUnique({ where: { username: opts.username } });
|
|
436
|
+
if (!user) {
|
|
437
|
+
console.error(`用户 '${opts.username}' 不存在`);
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
// 1. Update system password
|
|
441
|
+
execSync(`echo '${opts.username}:${opts.password}' | chpasswd`, { stdio: 'pipe' });
|
|
442
|
+
console.log(` 系统密码已更新`);
|
|
443
|
+
// 2. Update Samba password
|
|
444
|
+
try {
|
|
445
|
+
execSync(`echo -e '${opts.password}\\n${opts.password}' | smbpasswd -a -s ${opts.username}`, { stdio: 'pipe' });
|
|
446
|
+
console.log(` Samba 密码已更新`);
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
console.log(` Samba 密码跳过 (Samba 未安装)`);
|
|
450
|
+
}
|
|
451
|
+
// 3. Update DB password
|
|
452
|
+
const hashed = await bcrypt.hash(opts.password, 10);
|
|
453
|
+
await prisma.user.update({
|
|
454
|
+
where: { id: user.id },
|
|
455
|
+
data: { password: hashed },
|
|
456
|
+
});
|
|
457
|
+
console.log(`\n用户 '${opts.username}' 密码已修改`);
|
|
458
|
+
}
|
|
459
|
+
catch (err) {
|
|
460
|
+
console.error(`修改密码失败: ${err.message}`);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
finally {
|
|
464
|
+
await prisma.$disconnect();
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
userCmd
|
|
468
|
+
.command('set-quota')
|
|
469
|
+
.description('修改用户磁盘配额')
|
|
470
|
+
.requiredOption('-u, --username <username>', '用户名')
|
|
471
|
+
.requiredOption('-q, --quota <gb>', '配额大小 (GB)')
|
|
472
|
+
.action(async (opts) => {
|
|
473
|
+
const envFile = `${ETC_DIR}/.env`;
|
|
474
|
+
if (fs_1.default.existsSync(envFile)) {
|
|
475
|
+
require('dotenv').config({ path: envFile });
|
|
476
|
+
}
|
|
477
|
+
const { PrismaClient } = require('@prisma/client');
|
|
478
|
+
const { execSync } = require('child_process');
|
|
479
|
+
const quotaGB = parseInt(opts.quota);
|
|
480
|
+
if (isNaN(quotaGB) || quotaGB <= 0) {
|
|
481
|
+
console.error('配额必须是正整数');
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
const prisma = new PrismaClient();
|
|
485
|
+
await prisma.$connect();
|
|
486
|
+
try {
|
|
487
|
+
const user = await prisma.user.findUnique({ where: { username: opts.username } });
|
|
488
|
+
if (!user) {
|
|
489
|
+
console.error(`用户 '${opts.username}' 不存在`);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
// 1. Update xfs_quota
|
|
493
|
+
const shareBase = process.env.SHARE_BASE || '/share';
|
|
494
|
+
try {
|
|
495
|
+
execSync(`xfs_quota -x -c "limit -u bsoft=${quotaGB}g bhard=${quotaGB}g ${opts.username}" ${shareBase}`, { stdio: 'pipe' });
|
|
496
|
+
console.log(` 磁盘配额已设置: ${quotaGB}GB`);
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
console.log(` 配额设置跳过 (非 XFS 文件系统)`);
|
|
500
|
+
}
|
|
501
|
+
// 2. Update DB
|
|
502
|
+
await prisma.user.update({
|
|
503
|
+
where: { id: user.id },
|
|
504
|
+
data: { quotaSoft: quotaGB, quotaHard: quotaGB },
|
|
505
|
+
});
|
|
506
|
+
console.log(`\n用户 '${opts.username}' 配额已更新为 ${quotaGB}GB`);
|
|
507
|
+
}
|
|
508
|
+
catch (err) {
|
|
509
|
+
console.error(`修改配额失败: ${err.message}`);
|
|
510
|
+
process.exit(1);
|
|
511
|
+
}
|
|
512
|
+
finally {
|
|
513
|
+
await prisma.$disconnect();
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
program
|
|
517
|
+
.command('backup')
|
|
518
|
+
.description('备份数据库')
|
|
519
|
+
.option('-o, --output <path>', '备份文件路径', `./cloudisk-backup-${new Date().toISOString().slice(0, 10)}.db`)
|
|
520
|
+
.action((opts) => {
|
|
521
|
+
const dbFile = `${ETC_DIR}/data/cloud_storage.db`;
|
|
522
|
+
if (!fs_1.default.existsSync(dbFile)) {
|
|
523
|
+
console.error(`数据库文件不存在: ${dbFile}`);
|
|
524
|
+
process.exit(1);
|
|
525
|
+
}
|
|
526
|
+
const dest = path_1.default.resolve(opts.output);
|
|
527
|
+
fs_1.default.copyFileSync(dbFile, dest);
|
|
528
|
+
console.log(`数据库已备份到: ${dest}`);
|
|
529
|
+
console.log(`文件大小: ${(fs_1.default.statSync(dest).size / 1024).toFixed(1)} KB`);
|
|
530
|
+
});
|
|
531
|
+
program
|
|
532
|
+
.command('restore')
|
|
533
|
+
.description('恢复数据库')
|
|
534
|
+
.requiredOption('-f, --file <path>', '备份文件路径')
|
|
535
|
+
.option('--force', '跳过确认提示')
|
|
536
|
+
.action(async (opts) => {
|
|
537
|
+
const backupFile = path_1.default.resolve(opts.file);
|
|
538
|
+
if (!fs_1.default.existsSync(backupFile)) {
|
|
539
|
+
console.error(`备份文件不存在: ${backupFile}`);
|
|
540
|
+
process.exit(1);
|
|
541
|
+
}
|
|
542
|
+
const dbFile = `${ETC_DIR}/data/cloud_storage.db`;
|
|
543
|
+
const dbDir = `${ETC_DIR}/data`;
|
|
544
|
+
if (!opts.force) {
|
|
545
|
+
console.log(`即将用 ${backupFile} 覆盖数据库`);
|
|
546
|
+
console.log('当前数据库将备份为 cloud_storage.db.bak');
|
|
547
|
+
console.log('如需跳过确认,请使用 --force 参数');
|
|
548
|
+
const readline = require('readline');
|
|
549
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
550
|
+
const answer = await new Promise((resolve) => {
|
|
551
|
+
rl.question('确认恢复?(y/N): ', resolve);
|
|
552
|
+
});
|
|
553
|
+
rl.close();
|
|
554
|
+
if (answer.toLowerCase() !== 'y') {
|
|
555
|
+
console.log('已取消');
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
// Backup current DB
|
|
560
|
+
if (fs_1.default.existsSync(dbFile)) {
|
|
561
|
+
fs_1.default.copyFileSync(dbFile, `${dbFile}.bak`);
|
|
562
|
+
console.log('当前数据库已备份为 cloud_storage.db.bak');
|
|
563
|
+
}
|
|
564
|
+
// Ensure directory exists
|
|
565
|
+
if (!fs_1.default.existsSync(dbDir)) {
|
|
566
|
+
fs_1.default.mkdirSync(dbDir, { recursive: true });
|
|
567
|
+
}
|
|
568
|
+
fs_1.default.copyFileSync(backupFile, dbFile);
|
|
569
|
+
console.log(`数据库已恢复: ${dbFile}`);
|
|
570
|
+
console.log('请重启服务使恢复生效');
|
|
571
|
+
});
|
|
572
|
+
program
|
|
573
|
+
.command('status')
|
|
574
|
+
.description('查看系统状态')
|
|
575
|
+
.action(async () => {
|
|
576
|
+
const { execSync } = require('child_process');
|
|
577
|
+
console.log('=== Cloudisk 系统状态 ===\n');
|
|
578
|
+
// Database
|
|
579
|
+
const dbFile = `${ETC_DIR}/data/cloud_storage.db`;
|
|
580
|
+
if (fs_1.default.existsSync(dbFile)) {
|
|
581
|
+
const stat = fs_1.default.statSync(dbFile);
|
|
582
|
+
console.log(`数据库: 正常 (${(stat.size / 1024).toFixed(1)} KB)`);
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
console.log('数据库: 未找到');
|
|
586
|
+
}
|
|
587
|
+
// Config
|
|
588
|
+
const envFile = `${ETC_DIR}/.env`;
|
|
589
|
+
console.log(`配置文件: ${fs_1.default.existsSync(envFile) ? '存在' : '未找到'}`);
|
|
590
|
+
// Log file
|
|
591
|
+
const logFile = `${ETC_DIR}/logs/app.log`;
|
|
592
|
+
if (fs_1.default.existsSync(logFile)) {
|
|
593
|
+
const stat = fs_1.default.statSync(logFile);
|
|
594
|
+
console.log(`日志文件: ${(stat.size / 1024 / 1024).toFixed(1)} MB`);
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
console.log('日志文件: 未找到');
|
|
598
|
+
}
|
|
599
|
+
// Samba
|
|
600
|
+
console.log('');
|
|
601
|
+
try {
|
|
602
|
+
const sambaInstalled = execSync('dpkg-query -W -f=${Status} samba 2>/dev/null || rpm -q samba 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
603
|
+
console.log(`Samba: ${sambaInstalled.includes('installed') || sambaInstalled.startsWith('samba-') ? '已安装' : '未安装'}`);
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
console.log('Samba: 未安装');
|
|
607
|
+
}
|
|
608
|
+
try {
|
|
609
|
+
const smbStatus = execSync('systemctl is-active smbd 2>/dev/null || systemctl is-active smb 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
610
|
+
console.log(`Samba 服务: ${smbStatus === 'active' ? '运行中' : smbStatus}`);
|
|
611
|
+
}
|
|
612
|
+
catch {
|
|
613
|
+
console.log('Samba 服务: 未运行');
|
|
614
|
+
}
|
|
615
|
+
// Share directory
|
|
616
|
+
const shareBase = process.env.SHARE_BASE || '/share';
|
|
617
|
+
console.log('');
|
|
618
|
+
console.log(`共享目录: ${fs_1.default.existsSync(shareBase) ? shareBase : '未创建'}`);
|
|
619
|
+
if (fs_1.default.existsSync(shareBase)) {
|
|
620
|
+
try {
|
|
621
|
+
const mountCheck = execSync(`mountpoint -q ${shareBase} && echo mounted || echo not-mounted`, { encoding: 'utf-8' }).trim();
|
|
622
|
+
console.log(`磁盘挂载: ${mountCheck === 'mounted' ? '已挂载' : '未挂载'}`);
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
console.log('磁盘挂载: 未知');
|
|
626
|
+
}
|
|
627
|
+
try {
|
|
628
|
+
const df = execSync(`df -h ${shareBase} | tail -1`, { encoding: 'utf-8' }).trim();
|
|
629
|
+
const parts = df.split(/\s+/);
|
|
630
|
+
if (parts.length >= 5) {
|
|
631
|
+
console.log(`磁盘用量: ${parts[2]} / ${parts[1]} (${parts[4]})`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
catch { }
|
|
635
|
+
}
|
|
636
|
+
// Service port
|
|
637
|
+
console.log('');
|
|
638
|
+
try {
|
|
639
|
+
const port = execSync('ss -tlnp | grep :9528 | wc -l', { encoding: 'utf-8' }).trim();
|
|
640
|
+
console.log(`服务端口 9528: ${parseInt(port) > 0 ? '监听中' : '未监听'}`);
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
console.log('服务端口 9528: 未知');
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
const serverCmd = program.command('server').description('服务管理');
|
|
647
|
+
serverCmd
|
|
648
|
+
.command('start')
|
|
649
|
+
.description('启动服务')
|
|
650
|
+
.option('-p, --port <number>', '服务端口', '9528')
|
|
651
|
+
.option('-c, --config <path>', '配置文件路径', `${ETC_DIR}/.env`)
|
|
652
|
+
.option('-d, --db <path>', 'SQLite 数据库路径')
|
|
653
|
+
.option('-l, --log-level <level>', '日志级别 (debug/info/warn/error)', 'info')
|
|
654
|
+
.action(async (opts) => {
|
|
655
|
+
const envPath = path_1.default.resolve(opts.config);
|
|
656
|
+
if (fs_1.default.existsSync(envPath)) {
|
|
657
|
+
require('dotenv').config({ path: envPath });
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
const localEnv = path_1.default.join(__dirname, '../.env');
|
|
661
|
+
if (fs_1.default.existsSync(localEnv)) {
|
|
662
|
+
require('dotenv').config({ path: localEnv });
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (opts.port)
|
|
666
|
+
process.env.PORT = opts.port;
|
|
667
|
+
if (opts.db)
|
|
668
|
+
process.env.DATABASE_URL = `file:${opts.db}`;
|
|
669
|
+
if (opts.logLevel)
|
|
670
|
+
process.env.LOG_LEVEL = opts.logLevel;
|
|
671
|
+
await startServer();
|
|
672
|
+
});
|
|
673
|
+
serverCmd
|
|
674
|
+
.command('install-service')
|
|
675
|
+
.description('注册 systemd 服务')
|
|
676
|
+
.action(() => {
|
|
677
|
+
const binPath = process.execPath;
|
|
678
|
+
const scriptPath = path_1.default.resolve(__filename);
|
|
679
|
+
const service = `[Unit]
|
|
680
|
+
Description=Cloudisk
|
|
681
|
+
After=network.target
|
|
682
|
+
|
|
683
|
+
[Service]
|
|
684
|
+
Type=simple
|
|
685
|
+
ExecStart=${binPath} ${scriptPath} server start
|
|
686
|
+
WorkingDirectory=${ETC_DIR}
|
|
687
|
+
Restart=on-failure
|
|
688
|
+
RestartSec=5
|
|
689
|
+
|
|
690
|
+
[Install]
|
|
691
|
+
WantedBy=multi-user.target
|
|
692
|
+
`;
|
|
693
|
+
const target = '/etc/systemd/system/cloudisk.service';
|
|
694
|
+
try {
|
|
695
|
+
fs_1.default.writeFileSync(target, service);
|
|
696
|
+
require('child_process').execSync('systemctl daemon-reload');
|
|
697
|
+
console.log('systemd 服务已注册');
|
|
698
|
+
console.log(` 服务文件: ${target}`);
|
|
699
|
+
console.log('');
|
|
700
|
+
console.log('启动服务:');
|
|
701
|
+
console.log(' systemctl start cloudisk');
|
|
702
|
+
console.log('');
|
|
703
|
+
console.log('设置开机自启:');
|
|
704
|
+
console.log(' systemctl enable cloudisk');
|
|
705
|
+
}
|
|
706
|
+
catch (err) {
|
|
707
|
+
console.error('注册服务失败:', err.message);
|
|
708
|
+
console.error('请确保以 root 权限运行');
|
|
709
|
+
process.exit(1);
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
async function startServer() {
|
|
713
|
+
const express = (await Promise.resolve().then(() => __importStar(require('express')))).default;
|
|
714
|
+
const { PrismaClient } = await Promise.resolve().then(() => __importStar(require('@prisma/client')));
|
|
715
|
+
const { registerRoutes } = await Promise.resolve().then(() => __importStar(require('./routes')));
|
|
716
|
+
const { errorHandler } = await Promise.resolve().then(() => __importStar(require('./middleware/error-handler')));
|
|
717
|
+
const { logger } = await Promise.resolve().then(() => __importStar(require('./middleware/logger')));
|
|
718
|
+
const { config } = await Promise.resolve().then(() => __importStar(require('./config')));
|
|
719
|
+
const { setJwtSecret } = await Promise.resolve().then(() => __importStar(require('./middleware/auth')));
|
|
720
|
+
// Ensure directories
|
|
721
|
+
for (const dir of [config.database.dir, config.log.dir]) {
|
|
722
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
723
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
// Init Prisma
|
|
727
|
+
const prisma = new PrismaClient();
|
|
728
|
+
await prisma.$connect();
|
|
729
|
+
logger.info('数据库连接成功');
|
|
730
|
+
// Sync schema
|
|
731
|
+
const { execSync } = require('child_process');
|
|
732
|
+
try {
|
|
733
|
+
execSync('npx prisma db push --skip-generate', { cwd: __dirname + '/..', stdio: 'pipe' });
|
|
734
|
+
}
|
|
735
|
+
catch {
|
|
736
|
+
logger.warn('数据库 schema 同步失败,请手动运行: npx prisma db push');
|
|
737
|
+
}
|
|
738
|
+
// Load JWT secret from DB (if initialized)
|
|
739
|
+
try {
|
|
740
|
+
const setting = await prisma.setting.findUnique({ where: { key: 'jwtSecret' } });
|
|
741
|
+
if (setting) {
|
|
742
|
+
setJwtSecret(setting.value);
|
|
743
|
+
logger.info('从数据库加载认证密钥');
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
logger.warn('系统未初始化,等待初始化向导');
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
catch {
|
|
750
|
+
logger.warn('读取认证密钥失败,等待初始化');
|
|
751
|
+
}
|
|
752
|
+
// Express app
|
|
753
|
+
const app = express();
|
|
754
|
+
app.use(express.json({ limit: '10mb' }));
|
|
755
|
+
app.use(express.urlencoded({ extended: true }));
|
|
756
|
+
app.use((req, res, next) => {
|
|
757
|
+
const start = Date.now();
|
|
758
|
+
res.on('finish', () => {
|
|
759
|
+
const duration = Date.now() - start;
|
|
760
|
+
logger.info({
|
|
761
|
+
method: req.method,
|
|
762
|
+
url: req.url,
|
|
763
|
+
status: res.statusCode,
|
|
764
|
+
duration: `${duration}ms`,
|
|
765
|
+
ip: req.ip,
|
|
766
|
+
ua: req.headers['user-agent'],
|
|
767
|
+
}, `${req.method} ${req.url} ${res.statusCode} ${duration}ms`);
|
|
768
|
+
});
|
|
769
|
+
next();
|
|
770
|
+
});
|
|
771
|
+
// API routes
|
|
772
|
+
app.use(registerRoutes(prisma));
|
|
773
|
+
// Static files
|
|
774
|
+
const publicDir = path_1.default.join(__dirname, '../public');
|
|
775
|
+
if (fs_1.default.existsSync(publicDir)) {
|
|
776
|
+
app.use(express.static(publicDir));
|
|
777
|
+
app.get('/{*path}', (_req, res) => {
|
|
778
|
+
res.sendFile(path_1.default.join(publicDir, 'index.html'));
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
app.use(errorHandler);
|
|
782
|
+
app.listen(config.port, () => {
|
|
783
|
+
logger.info(`服务启动成功: http://0.0.0.0:${config.port}`);
|
|
784
|
+
logger.info(`环境: ${config.nodeEnv}`);
|
|
785
|
+
logger.info(`数据目录: ${ETC_DIR}`);
|
|
786
|
+
});
|
|
787
|
+
process.on('SIGINT', async () => {
|
|
788
|
+
logger.info('正在关闭服务...');
|
|
789
|
+
await prisma.$disconnect();
|
|
790
|
+
process.exit(0);
|
|
791
|
+
});
|
|
792
|
+
process.on('SIGTERM', async () => {
|
|
793
|
+
logger.info('正在关闭服务...');
|
|
794
|
+
await prisma.$disconnect();
|
|
795
|
+
process.exit(0);
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
program.parse();
|
|
799
|
+
//# sourceMappingURL=index.js.map
|