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.
Files changed (189) hide show
  1. package/.env.example +17 -0
  2. package/README.md +120 -0
  3. package/dist/config/index.d.ts +27 -0
  4. package/dist/config/index.d.ts.map +1 -0
  5. package/dist/config/index.js +45 -0
  6. package/dist/config/index.js.map +1 -0
  7. package/dist/dto/auth.dto.d.ts +14 -0
  8. package/dist/dto/auth.dto.d.ts.map +1 -0
  9. package/dist/dto/auth.dto.js +16 -0
  10. package/dist/dto/auth.dto.js.map +1 -0
  11. package/dist/dto/user.dto.d.ts +76 -0
  12. package/dist/dto/user.dto.d.ts.map +1 -0
  13. package/dist/dto/user.dto.js +70 -0
  14. package/dist/dto/user.dto.js.map +1 -0
  15. package/dist/handlers/auth.handler.d.ts +10 -0
  16. package/dist/handlers/auth.handler.d.ts.map +1 -0
  17. package/dist/handlers/auth.handler.js +41 -0
  18. package/dist/handlers/auth.handler.js.map +1 -0
  19. package/dist/handlers/disk.handler.d.ts +12 -0
  20. package/dist/handlers/disk.handler.d.ts.map +1 -0
  21. package/dist/handlers/disk.handler.js +61 -0
  22. package/dist/handlers/disk.handler.js.map +1 -0
  23. package/dist/handlers/samba.handler.d.ts +16 -0
  24. package/dist/handlers/samba.handler.d.ts.map +1 -0
  25. package/dist/handlers/samba.handler.js +120 -0
  26. package/dist/handlers/samba.handler.js.map +1 -0
  27. package/dist/handlers/share.handler.d.ts +11 -0
  28. package/dist/handlers/share.handler.d.ts.map +1 -0
  29. package/dist/handlers/share.handler.js +52 -0
  30. package/dist/handlers/share.handler.js.map +1 -0
  31. package/dist/handlers/system.handler.d.ts +17 -0
  32. package/dist/handlers/system.handler.d.ts.map +1 -0
  33. package/dist/handlers/system.handler.js +210 -0
  34. package/dist/handlers/system.handler.js.map +1 -0
  35. package/dist/handlers/user.handler.d.ts +16 -0
  36. package/dist/handlers/user.handler.d.ts.map +1 -0
  37. package/dist/handlers/user.handler.js +101 -0
  38. package/dist/handlers/user.handler.js.map +1 -0
  39. package/dist/index.d.ts +3 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +799 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/lib/errors.d.ts +13 -0
  44. package/dist/lib/errors.d.ts.map +1 -0
  45. package/dist/lib/errors.js +35 -0
  46. package/dist/lib/errors.js.map +1 -0
  47. package/dist/lib/log-buffer.d.ts +20 -0
  48. package/dist/lib/log-buffer.d.ts.map +1 -0
  49. package/dist/lib/log-buffer.js +75 -0
  50. package/dist/lib/log-buffer.js.map +1 -0
  51. package/dist/lib/response.d.ts +5 -0
  52. package/dist/lib/response.d.ts.map +1 -0
  53. package/dist/lib/response.js +19 -0
  54. package/dist/lib/response.js.map +1 -0
  55. package/dist/lib/shell.d.ts +9 -0
  56. package/dist/lib/shell.d.ts.map +1 -0
  57. package/dist/lib/shell.js +32 -0
  58. package/dist/lib/shell.js.map +1 -0
  59. package/dist/middleware/auth.d.ts +17 -0
  60. package/dist/middleware/auth.d.ts.map +1 -0
  61. package/dist/middleware/auth.js +43 -0
  62. package/dist/middleware/auth.js.map +1 -0
  63. package/dist/middleware/error-handler.d.ts +3 -0
  64. package/dist/middleware/error-handler.d.ts.map +1 -0
  65. package/dist/middleware/error-handler.js +14 -0
  66. package/dist/middleware/error-handler.js.map +1 -0
  67. package/dist/middleware/logger.d.ts +3 -0
  68. package/dist/middleware/logger.d.ts.map +1 -0
  69. package/dist/middleware/logger.js +25 -0
  70. package/dist/middleware/logger.js.map +1 -0
  71. package/dist/middleware/validate.d.ts +3 -0
  72. package/dist/middleware/validate.d.ts.map +1 -0
  73. package/dist/middleware/validate.js +28 -0
  74. package/dist/middleware/validate.js.map +1 -0
  75. package/dist/operators/disk.operator.d.ts +29 -0
  76. package/dist/operators/disk.operator.d.ts.map +1 -0
  77. package/dist/operators/disk.operator.js +70 -0
  78. package/dist/operators/disk.operator.js.map +1 -0
  79. package/dist/operators/linux-user.operator.d.ts +13 -0
  80. package/dist/operators/linux-user.operator.d.ts.map +1 -0
  81. package/dist/operators/linux-user.operator.js +47 -0
  82. package/dist/operators/linux-user.operator.js.map +1 -0
  83. package/dist/operators/quota.operator.d.ts +18 -0
  84. package/dist/operators/quota.operator.d.ts.map +1 -0
  85. package/dist/operators/quota.operator.js +54 -0
  86. package/dist/operators/quota.operator.js.map +1 -0
  87. package/dist/operators/samba.operator.d.ts +41 -0
  88. package/dist/operators/samba.operator.d.ts.map +1 -0
  89. package/dist/operators/samba.operator.js +274 -0
  90. package/dist/operators/samba.operator.js.map +1 -0
  91. package/dist/operators/share.operator.d.ts +21 -0
  92. package/dist/operators/share.operator.d.ts.map +1 -0
  93. package/dist/operators/share.operator.js +67 -0
  94. package/dist/operators/share.operator.js.map +1 -0
  95. package/dist/repositories/admin.repo.d.ts +30 -0
  96. package/dist/repositories/admin.repo.d.ts.map +1 -0
  97. package/dist/repositories/admin.repo.js +20 -0
  98. package/dist/repositories/admin.repo.js.map +1 -0
  99. package/dist/repositories/user.repo.d.ts +44 -0
  100. package/dist/repositories/user.repo.d.ts.map +1 -0
  101. package/dist/repositories/user.repo.js +3 -0
  102. package/dist/repositories/user.repo.js.map +1 -0
  103. package/dist/repositories/user.repo.sqlite.d.ts +16 -0
  104. package/dist/repositories/user.repo.sqlite.d.ts.map +1 -0
  105. package/dist/repositories/user.repo.sqlite.js +47 -0
  106. package/dist/repositories/user.repo.sqlite.js.map +1 -0
  107. package/dist/routes/auth.routes.d.ts +4 -0
  108. package/dist/routes/auth.routes.d.ts.map +1 -0
  109. package/dist/routes/auth.routes.js +15 -0
  110. package/dist/routes/auth.routes.js.map +1 -0
  111. package/dist/routes/disk.routes.d.ts +4 -0
  112. package/dist/routes/disk.routes.d.ts.map +1 -0
  113. package/dist/routes/disk.routes.js +16 -0
  114. package/dist/routes/disk.routes.js.map +1 -0
  115. package/dist/routes/index.d.ts +4 -0
  116. package/dist/routes/index.d.ts.map +1 -0
  117. package/dist/routes/index.js +64 -0
  118. package/dist/routes/index.js.map +1 -0
  119. package/dist/routes/samba.routes.d.ts +4 -0
  120. package/dist/routes/samba.routes.d.ts.map +1 -0
  121. package/dist/routes/samba.routes.js +20 -0
  122. package/dist/routes/samba.routes.js.map +1 -0
  123. package/dist/routes/share.routes.d.ts +4 -0
  124. package/dist/routes/share.routes.d.ts.map +1 -0
  125. package/dist/routes/share.routes.js +15 -0
  126. package/dist/routes/share.routes.js.map +1 -0
  127. package/dist/routes/system.routes.d.ts +4 -0
  128. package/dist/routes/system.routes.d.ts.map +1 -0
  129. package/dist/routes/system.routes.js +20 -0
  130. package/dist/routes/system.routes.js.map +1 -0
  131. package/dist/routes/user.routes.d.ts +5 -0
  132. package/dist/routes/user.routes.d.ts.map +1 -0
  133. package/dist/routes/user.routes.js +44 -0
  134. package/dist/routes/user.routes.js.map +1 -0
  135. package/dist/services/auth.service.d.ts +23 -0
  136. package/dist/services/auth.service.d.ts.map +1 -0
  137. package/dist/services/auth.service.js +62 -0
  138. package/dist/services/auth.service.js.map +1 -0
  139. package/dist/services/disk.service.d.ts +12 -0
  140. package/dist/services/disk.service.d.ts.map +1 -0
  141. package/dist/services/disk.service.js +34 -0
  142. package/dist/services/disk.service.js.map +1 -0
  143. package/dist/services/init.service.d.ts +8 -0
  144. package/dist/services/init.service.d.ts.map +1 -0
  145. package/dist/services/init.service.js +47 -0
  146. package/dist/services/init.service.js.map +1 -0
  147. package/dist/services/quota.service.d.ts +14 -0
  148. package/dist/services/quota.service.d.ts.map +1 -0
  149. package/dist/services/quota.service.js +32 -0
  150. package/dist/services/quota.service.js.map +1 -0
  151. package/dist/services/samba.service.d.ts +45 -0
  152. package/dist/services/samba.service.d.ts.map +1 -0
  153. package/dist/services/samba.service.js +61 -0
  154. package/dist/services/samba.service.js.map +1 -0
  155. package/dist/services/share.service.d.ts +18 -0
  156. package/dist/services/share.service.d.ts.map +1 -0
  157. package/dist/services/share.service.js +29 -0
  158. package/dist/services/share.service.js.map +1 -0
  159. package/dist/services/user.service.d.ts +48 -0
  160. package/dist/services/user.service.d.ts.map +1 -0
  161. package/dist/services/user.service.js +196 -0
  162. package/dist/services/user.service.js.map +1 -0
  163. package/dist/types/index.d.ts +7 -0
  164. package/dist/types/index.d.ts.map +1 -0
  165. package/dist/types/index.js +23 -0
  166. package/dist/types/index.js.map +1 -0
  167. package/dist/types/service.interfaces.d.ts +45 -0
  168. package/dist/types/service.interfaces.d.ts.map +1 -0
  169. package/dist/types/service.interfaces.js +3 -0
  170. package/dist/types/service.interfaces.js.map +1 -0
  171. package/package.json +63 -0
  172. package/prisma/schema.prisma +53 -0
  173. package/public/assets/Dashboard-BupoWE_7.js +1 -0
  174. package/public/assets/DiskManage-BY6Sy7T6.js +1 -0
  175. package/public/assets/InitWizard-CA9epEkr.css +1 -0
  176. package/public/assets/InitWizard-D5Xj7dX0.js +1 -0
  177. package/public/assets/Layout-D-CPXnPP.js +1 -0
  178. package/public/assets/Layout-DaQY8YzE.css +1 -0
  179. package/public/assets/LogViewer-CIJZzoXw.css +1 -0
  180. package/public/assets/LogViewer-DDAIjW9J.js +1 -0
  181. package/public/assets/Login-D92i6dQA.js +1 -0
  182. package/public/assets/Login-DimFYijw.css +1 -0
  183. package/public/assets/ServiceManage-CIWLurL_.css +1 -0
  184. package/public/assets/ServiceManage-D-ZWP7Ry.js +1 -0
  185. package/public/assets/ShareManage-BMsFoGea.js +1 -0
  186. package/public/assets/UserList-CqeA7r2Y.js +1 -0
  187. package/public/assets/index-B-crAlzg.css +1 -0
  188. package/public/assets/index-CfC5GCM9.js +100 -0
  189. 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