9drive 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 (104) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +389 -0
  3. package/backend/dist/app.js +113 -0
  4. package/backend/dist/config/env.js +75 -0
  5. package/backend/dist/config/prisma.js +30 -0
  6. package/backend/dist/middleware/api-key.middleware.js +38 -0
  7. package/backend/dist/middleware/auth.middleware.js +21 -0
  8. package/backend/dist/middleware/error.middleware.js +7 -0
  9. package/backend/dist/modules/api-keys/api-key.routes.js +90 -0
  10. package/backend/dist/modules/audit-logs/audit-log.routes.js +20 -0
  11. package/backend/dist/modules/auth/auth.routes.js +201 -0
  12. package/backend/dist/modules/cdn/cdn.routes.js +105 -0
  13. package/backend/dist/modules/connected-accounts/connected-account.routes.js +313 -0
  14. package/backend/dist/modules/files/file.routes.js +1013 -0
  15. package/backend/dist/modules/files/stream-file.js +10 -0
  16. package/backend/dist/modules/files/stream-google-file.js +79 -0
  17. package/backend/dist/modules/folders/folder.routes.js +546 -0
  18. package/backend/dist/modules/google/google.service.js +485 -0
  19. package/backend/dist/modules/invites/invite.routes.js +127 -0
  20. package/backend/dist/modules/provider-configs/provider-config.routes.js +53 -0
  21. package/backend/dist/modules/public/public.routes.js +44 -0
  22. package/backend/dist/modules/public-api/public-api.routes.js +8 -0
  23. package/backend/dist/modules/s3/s3.service.js +104 -0
  24. package/backend/dist/modules/storage/storage.routes.js +108 -0
  25. package/backend/dist/modules/system/system.routes.js +663 -0
  26. package/backend/dist/modules/uploads/upload.routes.js +569 -0
  27. package/backend/dist/scripts/debug-query.js +19 -0
  28. package/backend/dist/scripts/seed-google-config-if-present.js +49 -0
  29. package/backend/dist/scripts/seed-google-config.js +40 -0
  30. package/backend/dist/scripts/test-api-upload.js +37 -0
  31. package/backend/dist/scripts/test-external-upload.js +181 -0
  32. package/backend/dist/scripts/test-s3-connection.js +33 -0
  33. package/backend/dist/server.js +7 -0
  34. package/backend/dist/utils/audit.js +20 -0
  35. package/backend/dist/utils/crypto.js +33 -0
  36. package/backend/dist/utils/jwt.js +15 -0
  37. package/backend/dist/utils/password.js +14 -0
  38. package/backend/prisma/generated/sqlite-client/client.d.ts +1 -0
  39. package/backend/prisma/generated/sqlite-client/client.js +5 -0
  40. package/backend/prisma/generated/sqlite-client/default.d.ts +1 -0
  41. package/backend/prisma/generated/sqlite-client/default.js +5 -0
  42. package/backend/prisma/generated/sqlite-client/edge.d.ts +1 -0
  43. package/backend/prisma/generated/sqlite-client/edge.js +436 -0
  44. package/backend/prisma/generated/sqlite-client/index-browser.js +422 -0
  45. package/backend/prisma/generated/sqlite-client/index.d.ts +35178 -0
  46. package/backend/prisma/generated/sqlite-client/index.js +457 -0
  47. package/backend/prisma/generated/sqlite-client/package.json +183 -0
  48. package/backend/prisma/generated/sqlite-client/query_engine-windows.dll.node +0 -0
  49. package/backend/prisma/generated/sqlite-client/query_engine_bg.js +2 -0
  50. package/backend/prisma/generated/sqlite-client/query_engine_bg.wasm +0 -0
  51. package/backend/prisma/generated/sqlite-client/runtime/edge-esm.js +35 -0
  52. package/backend/prisma/generated/sqlite-client/runtime/edge.js +35 -0
  53. package/backend/prisma/generated/sqlite-client/runtime/index-browser.d.ts +370 -0
  54. package/backend/prisma/generated/sqlite-client/runtime/index-browser.js +17 -0
  55. package/backend/prisma/generated/sqlite-client/runtime/library.d.ts +3982 -0
  56. package/backend/prisma/generated/sqlite-client/runtime/library.js +147 -0
  57. package/backend/prisma/generated/sqlite-client/runtime/react-native.js +84 -0
  58. package/backend/prisma/generated/sqlite-client/runtime/wasm-compiler-edge.js +85 -0
  59. package/backend/prisma/generated/sqlite-client/runtime/wasm-engine-edge.js +38 -0
  60. package/backend/prisma/generated/sqlite-client/schema.prisma +354 -0
  61. package/backend/prisma/generated/sqlite-client/wasm-edge-light-loader.mjs +5 -0
  62. package/backend/prisma/generated/sqlite-client/wasm-worker-loader.mjs +5 -0
  63. package/backend/prisma/generated/sqlite-client/wasm.d.ts +1 -0
  64. package/backend/prisma/generated/sqlite-client/wasm.js +443 -0
  65. package/backend/prisma/migrations/20260604084325_init/migration.sql +191 -0
  66. package/backend/prisma/migrations/20260604090020_global_provider_config/migration.sql +8 -0
  67. package/backend/prisma/migrations/20260604091957_virtual_folders/migration.sql +32 -0
  68. package/backend/prisma/migrations/20260604095418_nested_folders/migration.sql +8 -0
  69. package/backend/prisma/migrations/20260604095705_file_shares/migration.sql +22 -0
  70. package/backend/prisma/migrations/20260604100802_file_preview_tokens/migration.sql +20 -0
  71. package/backend/prisma/migrations/20260604120000_add_file_share_token/migration.sql +3 -0
  72. package/backend/prisma/migrations/20260604130000_workspace_invites/migration.sql +17 -0
  73. package/backend/prisma/migrations/20260604133000_invite_targets/migration.sql +9 -0
  74. package/backend/prisma/migrations/20260605090000_query_indexes/migration.sql +12 -0
  75. package/backend/prisma/migrations/20260605100000_folder_icons/migration.sql +1 -0
  76. package/backend/prisma/migrations/20260605110000_google_auth_handoff/migration.sql +22 -0
  77. package/backend/prisma/migrations/20260611120000_s3_storage_support/migration.sql +31 -0
  78. package/backend/prisma/migrations/20260611130000_upload_routing_policy/migration.sql +14 -0
  79. package/backend/prisma/migrations/20260611140000_api_keys/migration.sql +21 -0
  80. package/backend/prisma/migrations/20260630030000_upload_session_resumable_fields/migration.sql +9 -0
  81. package/backend/prisma/migrations/migration_lock.toml +3 -0
  82. package/backend/prisma/schema.prisma +353 -0
  83. package/backend/prisma/schema.sqlite.prisma +354 -0
  84. package/backend/public/assets/index-B4ifIM_G.js +135 -0
  85. package/backend/public/assets/index-DwOPZrmt.css +2 -0
  86. package/backend/public/assets/ort-wasm-simd-threaded.jsep-D5Jk56-t.wasm +0 -0
  87. package/backend/public/assets/ort.bundle.min-CpXgll2e.js +2746 -0
  88. package/backend/public/assets/ort.bundle.min-OfoG_cy9.mjs +2817 -0
  89. package/backend/public/assets/ort.webgpu.bundle.min-C1vdBqT7.js +2746 -0
  90. package/backend/public/assets/ort.webgpu.bundle.min-CEayb2S6.mjs +2817 -0
  91. package/backend/public/assets/pdf.worker.min-Dswkl-cV.mjs +29 -0
  92. package/backend/public/favicon.ico +74 -0
  93. package/backend/public/favicon.svg +74 -0
  94. package/backend/public/icons.svg +24 -0
  95. package/backend/public/index.html +34 -0
  96. package/backend/public/manifest.webmanifest +1 -0
  97. package/backend/public/maskable-icon.svg +22 -0
  98. package/backend/public/pwa-192x192.svg +22 -0
  99. package/backend/public/pwa-512x512.svg +22 -0
  100. package/backend/public/registerSW.js +1 -0
  101. package/backend/public/sw.js +1 -0
  102. package/backend/public/workbox-9c191d2f.js +1 -0
  103. package/bin/9drive.js +320 -0
  104. package/package.json +60 -0
package/bin/9drive.js ADDED
@@ -0,0 +1,320 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * 9Drive CLI Launcher
5
+ * Zero-config Local-First Storage Cloud Server
6
+ */
7
+
8
+ const fs = require('fs')
9
+ const path = require('path')
10
+ const os = require('os')
11
+ const crypto = require('crypto')
12
+ const { spawn, execSync } = require('child_process')
13
+
14
+ // Version from root package.json
15
+ let version = '1.0.0'
16
+ try {
17
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'))
18
+ if (pkg.version) version = pkg.version
19
+ } catch {}
20
+
21
+ const BANNER = `
22
+ \x1b[36m ___ ___ _ \x1b[0m
23
+ \x1b[36m / _ \\ | \\ _ _(_)_ _____ \x1b[0m
24
+ \x1b[36m | (_) || |) | '_| \\ V / -_) \x1b[0m
25
+ \x1b[36m \\__\\_\\|___/|_| |_|\\_/\\___| \x1b[33mv${version}\x1b[0m
26
+ \x1b[90m Cloud Storage & Universal API Gateway\x1b[0m
27
+ `
28
+
29
+ function printHelp() {
30
+ console.log(BANNER)
31
+ console.log(`
32
+ Usage:
33
+ 9drive [command] [options]
34
+
35
+ Commands:
36
+ start Start the 9Drive local server (default)
37
+ status Show 9Drive data directory, database, and configuration
38
+ backup Create an instant backup of the local database
39
+ open Open the 9Drive Web UI in your default browser
40
+
41
+ Options:
42
+ --port <num> Port number to run 9Drive on (default: 9999 or from config)
43
+ --host <str> Host to bind server (default: localhost)
44
+ --db <url> Custom database URL (SQLite file:... or MySQL mysql://...)
45
+ --no-open Do not open the browser automatically upon launch
46
+ --help, -h Show this help message
47
+ --version, -v Show version number
48
+
49
+ Examples:
50
+ npx 9drive
51
+ npx 9drive start --port 8080
52
+ npx 9drive status
53
+ npx 9drive backup
54
+ `)
55
+ }
56
+
57
+ // Parse command line arguments
58
+ const args = process.argv.slice(2)
59
+ let command = 'start'
60
+ let portArg = null
61
+ let dbArg = null
62
+ let noOpen = false
63
+
64
+ for (let i = 0; i < args.length; i++) {
65
+ const arg = args[i]
66
+ if (arg === '--help' || arg === '-h') {
67
+ printHelp()
68
+ process.exit(0)
69
+ }
70
+ if (arg === '--version' || arg === '-v') {
71
+ console.log(`9Drive v${version}`)
72
+ process.exit(0)
73
+ }
74
+ if (arg === '--port') {
75
+ portArg = Number(args[++i])
76
+ } else if (arg === '--db') {
77
+ dbArg = args[++i]
78
+ } else if (arg === '--no-open') {
79
+ noOpen = true
80
+ } else if (!arg.startsWith('-') && i === 0) {
81
+ command = arg
82
+ }
83
+ }
84
+
85
+ // Setup user data directory: ~/.9drive/
86
+ const homeDir = os.homedir()
87
+ const dataDir = process.env.NINEDRIVE_DATA_DIR || path.join(homeDir, '.9drive')
88
+ const backupsDir = path.join(dataDir, 'backups')
89
+ const storageDir = path.join(dataDir, 'storage')
90
+ const configPath = path.join(dataDir, 'config.json')
91
+ const sqliteDbPath = path.join(dataDir, '9drive.db')
92
+
93
+ function ensureDirectories() {
94
+ if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true })
95
+ if (!fs.existsSync(backupsDir)) fs.mkdirSync(backupsDir, { recursive: true })
96
+ if (!fs.existsSync(storageDir)) fs.mkdirSync(storageDir, { recursive: true })
97
+ }
98
+
99
+ function loadOrCreateConfig() {
100
+ ensureDirectories()
101
+ let config = {}
102
+ if (fs.existsSync(configPath)) {
103
+ try {
104
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
105
+ } catch {}
106
+ }
107
+
108
+ const defaultPort = portArg || config.APP_PORT || 9999
109
+ const defaultDatabaseUrl = dbArg || config.DATABASE_URL || `file:${sqliteDbPath.replace(/\\/g, '/')}`
110
+ const defaultFrontendUrl = config.FRONTEND_URL || `http://localhost:${defaultPort}`
111
+ const defaultJwtSecret = config.JWT_ACCESS_SECRET || crypto.randomBytes(32).toString('hex')
112
+ const defaultEncryptionKey = config.TOKEN_ENCRYPTION_KEY || crypto.randomBytes(32).toString('hex')
113
+
114
+ const updatedConfig = {
115
+ APP_PORT: defaultPort,
116
+ DATABASE_URL: defaultDatabaseUrl,
117
+ FRONTEND_URL: defaultFrontendUrl,
118
+ JWT_ACCESS_SECRET: defaultJwtSecret,
119
+ TOKEN_ENCRYPTION_KEY: defaultEncryptionKey,
120
+ MAX_UPLOAD_BYTES: config.MAX_UPLOAD_BYTES || 5 * 1024 * 1024 * 1024,
121
+ ...config,
122
+ }
123
+
124
+ if (portArg) updatedConfig.APP_PORT = portArg
125
+ if (dbArg) updatedConfig.DATABASE_URL = dbArg
126
+
127
+ fs.writeFileSync(configPath, JSON.stringify(updatedConfig, null, 2))
128
+ return updatedConfig
129
+ }
130
+
131
+ function openBrowser(url) {
132
+ const start =
133
+ process.platform === 'darwin'
134
+ ? 'open'
135
+ : process.platform === 'win32'
136
+ ? 'start'
137
+ : 'xdg-open'
138
+ try {
139
+ execSync(`${start} ${url}`, { stdio: 'ignore' })
140
+ } catch {}
141
+ }
142
+
143
+ function handleStatus() {
144
+ ensureDirectories()
145
+ const config = loadOrCreateConfig()
146
+ console.log(BANNER)
147
+ console.log('\x1b[32m[9Drive Status]\x1b[0m')
148
+ console.log(` Version: v${version}`)
149
+ console.log(` Data Directory: ${dataDir}`)
150
+ console.log(` Config File: ${configPath}`)
151
+ console.log(` Database URL: ${config.DATABASE_URL}`)
152
+ console.log(` Configured Port: ${config.APP_PORT}`)
153
+
154
+ if (fs.existsSync(sqliteDbPath)) {
155
+ const stats = fs.statSync(sqliteDbPath)
156
+ console.log(` SQLite DB Size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`)
157
+ } else {
158
+ console.log(` SQLite DB Size: Not initialized yet`)
159
+ }
160
+
161
+ const backups = fs.existsSync(backupsDir) ? fs.readdirSync(backupsDir) : []
162
+ console.log(` Total Backups: ${backups.length} snapshot(s)`)
163
+ }
164
+
165
+ function handleBackup() {
166
+ ensureDirectories()
167
+ if (!fs.existsSync(sqliteDbPath)) {
168
+ console.log('\x1b[33m[!] No SQLite database found to backup yet.\x1b[0m')
169
+ return
170
+ }
171
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
172
+ const destPath = path.join(backupsDir, `9drive-backup-${timestamp}.db`)
173
+ fs.copyFileSync(sqliteDbPath, destPath)
174
+ console.log(`\x1b[32m[✓] Database backup created:\x1b[0m ${destPath}`)
175
+ }
176
+
177
+ async function handleStart() {
178
+ ensureDirectories()
179
+ const config = loadOrCreateConfig()
180
+
181
+ console.log(BANNER)
182
+ console.log(` \x1b[32m[✓]\x1b[0m Data directory: \x1b[90m${dataDir}\x1b[0m`)
183
+
184
+ const isSqlite = config.DATABASE_URL.startsWith('file:') || config.DATABASE_URL.startsWith('sqlite:')
185
+
186
+ // Auto-backup database on startup if existing SQLite database found
187
+ if (isSqlite && fs.existsSync(sqliteDbPath)) {
188
+ try {
189
+ const stats = fs.statSync(sqliteDbPath)
190
+ if (stats.size > 0) {
191
+ const timestamp = new Date().toISOString().slice(0, 10)
192
+ const autoBackup = path.join(backupsDir, `auto-backup-${timestamp}.db`)
193
+ fs.copyFileSync(sqliteDbPath, autoBackup)
194
+ }
195
+ } catch {}
196
+ }
197
+
198
+ // Resolve backend root and prisma
199
+ const backendDir = path.resolve(__dirname, '../backend')
200
+ const rootDir = path.resolve(__dirname, '..')
201
+
202
+ // Sync SQLite schema automatically if in SQLite mode
203
+ if (isSqlite) {
204
+ const sqliteSchemaPath = path.join(backendDir, 'prisma/schema.sqlite.prisma')
205
+ const sqliteClientIndex = path.join(backendDir, 'prisma/generated/sqlite-client/index.js')
206
+ const prismaCli = [
207
+ path.join(rootDir, 'node_modules/prisma/build/index.js'),
208
+ path.join(backendDir, 'node_modules/prisma/build/index.js'),
209
+ ].find((p) => fs.existsSync(p)) || null
210
+
211
+ if (fs.existsSync(sqliteSchemaPath) && prismaCli) {
212
+ if (!fs.existsSync(sqliteClientIndex)) {
213
+ process.stdout.write(' [⧗] Generating SQLite database client... ')
214
+ try {
215
+ execSync(`node "${prismaCli}" generate --schema="${sqliteSchemaPath}"`, {
216
+ stdio: 'ignore',
217
+ env: { ...process.env, DATABASE_URL: config.DATABASE_URL },
218
+ })
219
+ console.log('\x1b[32m[Ready]\x1b[0m')
220
+ } catch {
221
+ console.log('\x1b[33m[Notice: Generator skipped]\x1b[0m')
222
+ }
223
+ }
224
+
225
+ process.stdout.write(' [⧗] Checking database schema... ')
226
+ try {
227
+ execSync(
228
+ `node "${prismaCli}" db push --schema="${sqliteSchemaPath}" --skip-generate`,
229
+ {
230
+ stdio: 'ignore',
231
+ env: {
232
+ ...process.env,
233
+ DATABASE_URL: config.DATABASE_URL,
234
+ },
235
+ }
236
+ )
237
+ console.log('\x1b[32m[Ready]\x1b[0m')
238
+ } catch (err) {
239
+ console.log('\x1b[33m[Notice: Using existing schema]\x1b[0m')
240
+ }
241
+ }
242
+ }
243
+
244
+ // Locate server entry file (dist/server.js or src/server.ts)
245
+ let serverEntry = path.join(backendDir, 'dist/server.js')
246
+ let useTsx = false
247
+
248
+ if (!fs.existsSync(serverEntry)) {
249
+ serverEntry = path.join(backendDir, 'src/server.ts')
250
+ useTsx = true
251
+ }
252
+
253
+ const serverUrl = `http://localhost:${config.APP_PORT}`
254
+ console.log(` \x1b[32m[✓]\x1b[0m Web Dashboard: \x1b[36m\x1b[4m${serverUrl}\x1b[0m`)
255
+ console.log(` \x1b[90m[i] Press Ctrl+C to stop the server.\x1b[0m\n`)
256
+
257
+ const nodePath = [
258
+ path.join(backendDir, 'node_modules'),
259
+ path.join(rootDir, 'node_modules'),
260
+ process.env.NODE_PATH || '',
261
+ ].filter(Boolean).join(path.delimiter)
262
+
263
+ const envVars = {
264
+ ...process.env,
265
+ NODE_PATH: nodePath,
266
+ NINEDRIVE_CLI: 'true',
267
+ NINEDRIVE_DATA_DIR: dataDir,
268
+ APP_PORT: String(config.APP_PORT),
269
+ DATABASE_URL: config.DATABASE_URL,
270
+ FRONTEND_URL: config.FRONTEND_URL || serverUrl,
271
+ JWT_ACCESS_SECRET: config.JWT_ACCESS_SECRET,
272
+ TOKEN_ENCRYPTION_KEY: config.TOKEN_ENCRYPTION_KEY,
273
+ SERVE_FRONTEND: 'true',
274
+ }
275
+
276
+ const runner = useTsx
277
+ ? path.join(backendDir, 'node_modules/tsx/dist/cli.mjs')
278
+ : null
279
+
280
+ const child = runner
281
+ ? spawn('node', [runner, serverEntry], {
282
+ cwd: backendDir,
283
+ env: envVars,
284
+ stdio: 'inherit',
285
+ })
286
+ : spawn('node', [serverEntry], {
287
+ cwd: backendDir,
288
+ env: envVars,
289
+ stdio: 'inherit',
290
+ })
291
+
292
+ // Open browser after short delay
293
+ if (!noOpen) {
294
+ setTimeout(() => {
295
+ openBrowser(serverUrl)
296
+ }, 1500)
297
+ }
298
+
299
+ child.on('exit', (code) => {
300
+ process.exit(code || 0)
301
+ })
302
+ }
303
+
304
+ // Route commands
305
+ switch (command) {
306
+ case 'status':
307
+ handleStatus()
308
+ break
309
+ case 'backup':
310
+ handleBackup()
311
+ break
312
+ case 'open':
313
+ const conf = loadOrCreateConfig()
314
+ openBrowser(`http://localhost:${conf.APP_PORT}`)
315
+ break
316
+ case 'start':
317
+ default:
318
+ handleStart()
319
+ break
320
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "9drive",
3
+ "version": "1.0.0",
4
+ "description": "9Drive - Personal Cloud Storage & Universal API Gateway",
5
+ "main": "bin/9drive.js",
6
+ "bin": "bin/9drive.js",
7
+ "keywords": [
8
+ "cloud-storage",
9
+ "s3",
10
+ "google-drive",
11
+ "storage",
12
+ "self-hosted",
13
+ "local-first",
14
+ "drive",
15
+ "npx"
16
+ ],
17
+ "author": "Ninedrive",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/ninedrive/9drive.git"
22
+ },
23
+ "files": [
24
+ "bin",
25
+ "backend/dist",
26
+ "backend/public",
27
+ "backend/prisma",
28
+ "README.md"
29
+ ],
30
+ "scripts": {
31
+ "start": "node bin/9drive.js",
32
+ "dev": "concurrently -n \"backend,frontend\" -c \"blue,green\" \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
33
+ "dev:backend": "npm run dev --prefix backend",
34
+ "dev:frontend": "npm run dev --prefix frontend",
35
+ "install:all": "npm install --prefix backend && npm install --prefix frontend",
36
+ "build": "npm run build --prefix backend && npm run build --prefix frontend",
37
+ "build:sqlite": "npm run prisma:generate:sqlite --prefix backend",
38
+ "bundle:cli": "node scripts/bundle-cli.js",
39
+ "prepack": "node scripts/bundle-cli.js"
40
+ },
41
+ "dependencies": {
42
+ "@aws-sdk/client-s3": "^3.954.0",
43
+ "@aws-sdk/lib-storage": "^3.954.0",
44
+ "@prisma/client": "^6.19.3",
45
+ "archiver": "^8.0.0",
46
+ "argon2": "^0.44.0",
47
+ "busboy": "^1.6.0",
48
+ "cors": "^2.8.6",
49
+ "dotenv": "^17.4.2",
50
+ "express": "^5.2.1",
51
+ "googleapis": "^173.0.0",
52
+ "jsonwebtoken": "^9.0.3",
53
+ "prisma": "^6.19.3",
54
+ "undici": "^7.27.1",
55
+ "zod": "^4.4.3"
56
+ },
57
+ "devDependencies": {
58
+ "concurrently": "^9.1.2"
59
+ }
60
+ }