@tothalex/nulljs 0.0.53 → 0.0.54

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.
@@ -1,330 +0,0 @@
1
- import type { Command } from 'commander'
2
- import { existsSync, writeFileSync, mkdirSync, readFileSync } from 'node:fs'
3
- import { join } from 'node:path'
4
- import { execSync } from 'node:child_process'
5
- import { homedir } from 'node:os'
6
- import chalk from 'chalk'
7
-
8
- type PlatformKey = 'linux-x64' | 'linux-arm64' | 'darwin-arm64'
9
-
10
- const PLATFORMS: Record<PlatformKey, string> = {
11
- 'linux-x64': '@tothalex/nulljs-linux-x64',
12
- 'linux-arm64': '@tothalex/nulljs-linux-arm64',
13
- 'darwin-arm64': '@tothalex/nulljs-darwin-arm64'
14
- }
15
-
16
- type ProductionConfig = {
17
- key: {
18
- private: string
19
- public: string
20
- }
21
- }
22
-
23
- const getPlatformKey = (): string => {
24
- return `${process.platform}-${process.arch}`
25
- }
26
-
27
- const getServerBinPath = (): string => {
28
- const platformKey = getPlatformKey()
29
- const pkgName = PLATFORMS[platformKey as PlatformKey]
30
-
31
- if (!pkgName) {
32
- throw new Error(`Unsupported platform: ${platformKey}`)
33
- }
34
-
35
- return require.resolve(`${pkgName}/bin/server`)
36
- }
37
-
38
- const generateProductionKeys = async (): Promise<{ privateKey: string; publicKey: string }> => {
39
- console.log(chalk.blue('Generating production keys...'))
40
-
41
- const keyPair = await crypto.subtle.generateKey(
42
- {
43
- name: 'Ed25519',
44
- namedCurve: 'Ed25519'
45
- },
46
- true,
47
- ['sign', 'verify']
48
- )
49
-
50
- const privateKeyBuffer = await crypto.subtle.exportKey('pkcs8', keyPair.privateKey)
51
- const publicKeyBuffer = await crypto.subtle.exportKey('spki', keyPair.publicKey)
52
-
53
- const privateKey = btoa(String.fromCharCode(...new Uint8Array(privateKeyBuffer)))
54
- const publicKey = btoa(String.fromCharCode(...new Uint8Array(publicKeyBuffer)))
55
-
56
- return { privateKey, publicKey }
57
- }
58
-
59
- const saveProductionConfig = (cloudPath: string, privateKey: string, publicKey: string): void => {
60
- const configPath = join(cloudPath, 'config.json')
61
- const config: ProductionConfig = {
62
- key: {
63
- private: privateKey,
64
- public: publicKey
65
- }
66
- }
67
-
68
- console.log(chalk.blue('Saving production configuration...'))
69
- writeFileSync(configPath, JSON.stringify(config, null, 2))
70
-
71
- // Set proper permissions on the config file (readable only by owner)
72
- execSync(`chmod 600 ${configPath}`, { stdio: 'inherit' })
73
-
74
- console.log(chalk.green('Production configuration saved'))
75
- }
76
-
77
- const createSystemdService = (
78
- cloudPath: string,
79
- publicKey: string,
80
- serverBinPath: string,
81
- userName: string
82
- ) => {
83
- const serviceContent = `[Unit]
84
- Description=NullJS Server
85
- After=network.target
86
- StartLimitIntervalSec=0
87
-
88
- [Service]
89
- Type=simple
90
- Restart=always
91
- RestartSec=1
92
- User=${userName}
93
- ExecStart=${serverBinPath}
94
- Environment=CLOUD_PATH=${cloudPath}
95
- Environment=PUBLIC_KEY=${publicKey}
96
- WorkingDirectory=${cloudPath}
97
-
98
- [Install]
99
- WantedBy=multi-user.target
100
- `
101
-
102
- return serviceContent
103
- }
104
-
105
- const ensureCloudDirectory = (cloudPath: string) => {
106
- if (!existsSync(cloudPath)) {
107
- console.log(chalk.blue('Creating cloud directory...'))
108
- try {
109
- mkdirSync(cloudPath, { recursive: true })
110
- console.log(chalk.green(`Cloud directory created: ${cloudPath}`))
111
- } catch (error) {
112
- console.log(
113
- chalk.red('Failed to create cloud directory:'),
114
- error instanceof Error ? error.message : String(error)
115
- )
116
- throw error
117
- }
118
- } else {
119
- console.log(chalk.gray(`Cloud directory already exists: ${cloudPath}`))
120
- }
121
- }
122
-
123
- const installSystemdService = (serviceContent: string) => {
124
- const servicePath = '/etc/systemd/system/nulljs.service'
125
-
126
- console.log(chalk.blue('Installing systemd service...'))
127
-
128
- // Write service file
129
- writeFileSync('/tmp/nulljs.service', serviceContent)
130
- execSync(`sudo mv /tmp/nulljs.service ${servicePath}`, { stdio: 'inherit' })
131
-
132
- // Reload systemd and enable service
133
- execSync('sudo systemctl daemon-reload', { stdio: 'inherit' })
134
- execSync('sudo systemctl enable nulljs.service', { stdio: 'inherit' })
135
-
136
- console.log(chalk.green('Systemd service installed and enabled'))
137
- }
138
-
139
- const validateLinux = () => {
140
- if (process.platform !== 'linux') {
141
- console.log(chalk.red('This command only works on Linux'))
142
- process.exit(1)
143
- }
144
- }
145
-
146
- const validateServerBinary = (serverBinPath: string) => {
147
- if (!existsSync(serverBinPath)) {
148
- console.log(chalk.red('Server binary not found at:'), serverBinPath)
149
- console.log(chalk.yellow('Make sure you have installed the package correctly'))
150
- process.exit(1)
151
- }
152
- }
153
-
154
- const checkExistingProductionConfig = (cloudPath: string): ProductionConfig | null => {
155
- const configPath = join(cloudPath, 'config.json')
156
-
157
- if (!existsSync(configPath)) {
158
- return null
159
- }
160
-
161
- try {
162
- const configContent = JSON.parse(readFileSync(configPath, 'utf8'))
163
- if (configContent.key?.private && configContent.key?.public) {
164
- return configContent as ProductionConfig
165
- }
166
- } catch {
167
- console.log(chalk.yellow('Existing production config is invalid, will regenerate'))
168
- }
169
-
170
- return null
171
- }
172
-
173
- const unhost = async (options: { keepData?: boolean } = {}) => {
174
- validateLinux()
175
-
176
- console.log(chalk.blue('Removing NullJS production hosting...'))
177
-
178
- try {
179
- // Stop and disable the systemd service
180
- console.log(chalk.blue('Stopping NullJS service...'))
181
- try {
182
- execSync('sudo systemctl stop nulljs', { stdio: 'inherit' })
183
- console.log(chalk.green('Service stopped'))
184
- } catch {
185
- console.log(chalk.yellow('Service was not running'))
186
- }
187
-
188
- try {
189
- execSync('sudo systemctl disable nulljs', { stdio: 'inherit' })
190
- console.log(chalk.green('Service disabled'))
191
- } catch {
192
- console.log(chalk.yellow('Service was not enabled'))
193
- }
194
-
195
- // Remove the systemd service file
196
- const servicePath = '/etc/systemd/system/nulljs.service'
197
- if (existsSync(servicePath)) {
198
- console.log(chalk.blue('Removing systemd service file...'))
199
- execSync(`sudo rm ${servicePath}`, { stdio: 'inherit' })
200
- execSync('sudo systemctl daemon-reload', { stdio: 'inherit' })
201
- console.log(chalk.green('Systemd service file removed'))
202
- }
203
-
204
- // Remove cloud directory (optional)
205
- if (!options.keepData) {
206
- const defaultCloudPath = join(homedir(), '.nulljs')
207
- if (existsSync(defaultCloudPath)) {
208
- console.log(chalk.blue('Removing cloud directory...'))
209
- execSync(`rm -rf ${defaultCloudPath}`, { stdio: 'inherit' })
210
- console.log(chalk.green('Cloud directory removed'))
211
- } else {
212
- console.log(chalk.gray('Cloud directory does not exist'))
213
- }
214
- } else {
215
- console.log(chalk.gray('Keeping cloud data (--keep-data specified)'))
216
- }
217
-
218
- console.log(chalk.green('NullJS hosting cleanup complete!'))
219
- console.log('')
220
- console.log(chalk.blue('Summary:'))
221
- console.log(chalk.gray(' - Systemd service stopped and removed'))
222
- if (!options.keepData) {
223
- console.log(chalk.gray(' - Cloud directory removed'))
224
- }
225
- } catch (error) {
226
- console.log(
227
- chalk.red('Failed to remove hosting setup:'),
228
- error instanceof Error ? error.message : String(error)
229
- )
230
- process.exit(1)
231
- }
232
- }
233
-
234
- const host = async (cloudPath?: string) => {
235
- validateLinux()
236
-
237
- // Use provided cloud path or default to ~/.nulljs
238
- const resolvedCloudPath = cloudPath
239
- ? cloudPath.startsWith('/')
240
- ? cloudPath
241
- : join(process.cwd(), cloudPath)
242
- : join(homedir(), '.nulljs')
243
-
244
- console.log(chalk.blue('Setting up NullJS production hosting...'))
245
- console.log(chalk.gray(` Cloud path: ${resolvedCloudPath}`))
246
-
247
- const serverBinPath = getServerBinPath()
248
- validateServerBinary(serverBinPath)
249
-
250
- // Get current user
251
- const currentUser = process.env.USER || process.env.USERNAME || 'root'
252
- console.log(chalk.blue(`Setting up service to run as user: ${currentUser}`))
253
-
254
- // Ensure cloud directory exists
255
- ensureCloudDirectory(resolvedCloudPath)
256
-
257
- // Check for existing production config or generate new keys
258
- let productionConfig = checkExistingProductionConfig(resolvedCloudPath)
259
-
260
- if (!productionConfig) {
261
- const { privateKey, publicKey } = await generateProductionKeys()
262
- saveProductionConfig(resolvedCloudPath, privateKey, publicKey)
263
- productionConfig = { key: { private: privateKey, public: publicKey } }
264
-
265
- console.log(chalk.green('Production keys generated'))
266
- console.log(chalk.blue('Production Public Key:'))
267
- console.log(chalk.gray(productionConfig.key.public))
268
- } else {
269
- console.log(chalk.green('Using existing production keys'))
270
- console.log(chalk.blue('Production Public Key:'))
271
- console.log(chalk.gray(productionConfig.key.public))
272
- }
273
-
274
- // Create and install systemd service
275
- const serviceContent = createSystemdService(
276
- resolvedCloudPath,
277
- productionConfig.key.public,
278
- serverBinPath,
279
- currentUser
280
- )
281
- installSystemdService(serviceContent)
282
-
283
- console.log(chalk.green('NullJS hosting setup complete!'))
284
- console.log('')
285
- console.log(chalk.blue('Service management commands:'))
286
- console.log(chalk.gray(' Start: '), chalk.white('sudo systemctl start nulljs'))
287
- console.log(chalk.gray(' Stop: '), chalk.white('sudo systemctl stop nulljs'))
288
- console.log(chalk.gray(' Status: '), chalk.white('sudo systemctl status nulljs'))
289
- console.log(chalk.gray(' Logs: '), chalk.white('sudo journalctl -u nulljs -f'))
290
- console.log('')
291
- console.log(chalk.yellow('Remember to start the service: sudo systemctl start nulljs'))
292
- }
293
-
294
- export const registerHostCommand = (program: Command) => {
295
- const hostCmd = program
296
- .command('host')
297
- .description('Set up NullJS production hosting with systemd')
298
- .argument('[cloud-path]', 'Path for cloud storage (default: ~/.nulljs)')
299
- .action(async (cloudPath?: string) => {
300
- await host(cloudPath)
301
- })
302
-
303
- hostCmd
304
- .command('remove')
305
- .description('Remove NullJS production hosting')
306
- .option('--keep-data', 'Keep cloud data directory')
307
- .action(async (options: { keepData?: boolean }) => {
308
- await unhost(options)
309
- })
310
-
311
- hostCmd
312
- .command('logs')
313
- .description('View NullJS server logs')
314
- .option('-n, --lines <number>', 'Number of lines to show', '100')
315
- .option('--no-follow', 'Do not follow log output')
316
- .action((options: { lines: string; follow: boolean }) => {
317
- validateLinux()
318
-
319
- const args = ['-u', 'nulljs', '-n', options.lines]
320
- if (options.follow) {
321
- args.push('-f')
322
- }
323
-
324
- try {
325
- execSync(`journalctl ${args.join(' ')}`, { stdio: 'inherit' })
326
- } catch {
327
- // User likely pressed Ctrl+C, which is fine
328
- }
329
- })
330
- }
@@ -1,6 +0,0 @@
1
- export { registerDevCommand } from './dev'
2
- export { registerDeployCommand } from './deploy'
3
- export { registerConfigCommand } from './config'
4
- export { registerStatusCommand } from './status'
5
- export { registerSecretCommand } from './secret'
6
- export { registerHostCommand } from './host'