@ecmaos/coreutils 0.2.0 → 0.2.1
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +14 -0
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +82 -85
- package/dist/commands/ls.js.map +1 -1
- package/dist/commands/user.d.ts +4 -0
- package/dist/commands/user.d.ts.map +1 -0
- package/dist/commands/user.js +427 -0
- package/dist/commands/user.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/ls.ts +93 -79
- package/src/commands/user.ts +436 -0
- package/src/index.ts +3 -0
package/src/commands/ls.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
|
+
import columnify from 'columnify'
|
|
3
4
|
import humanFormat from 'human-format'
|
|
4
5
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
5
6
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
@@ -153,87 +154,100 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
153
154
|
.filter((entry, index, self) => self.findIndex(e => e?.name === entry?.name) === index)
|
|
154
155
|
.filter((entry): entry is NonNullable<typeof entry> => entry !== null && entry !== undefined)
|
|
155
156
|
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
157
|
+
const isDevDirectory = fullPath.startsWith('/dev')
|
|
158
|
+
const columns = isDevDirectory ? ['Name', 'Mode', 'Owner', 'Info'] : ['Name', 'Size', 'Modified', 'Mode', 'Owner', 'Info']
|
|
159
|
+
|
|
160
|
+
const directoryRows = directories.sort((a, b) => a.name.localeCompare(b.name)).map(directory => {
|
|
161
|
+
const displayName = directory.linkTarget
|
|
162
|
+
? `${directory.name} ${chalk.cyan('⟶')} ${directory.linkTarget}`
|
|
163
|
+
: directory.name
|
|
164
|
+
const modeStats = directory.linkStats && directory.linkStats.isSymbolicLink()
|
|
165
|
+
? directory.linkStats
|
|
166
|
+
: directory.stats
|
|
167
|
+
const modeString = modeStats
|
|
168
|
+
? getModeString(modeStats, directory.linkStats?.isSymbolicLink() ? directory.stats : undefined)
|
|
169
|
+
: ''
|
|
170
|
+
const linkInfo = getLinkInfo(directory.linkTarget, directory.linkStats, directory.stats)
|
|
171
|
+
|
|
172
|
+
const modeType = modeString?.charAt(0) || ''
|
|
173
|
+
const coloredName = modeType === 'd' ? chalk.blue(displayName)
|
|
174
|
+
: modeType === 'l' ? chalk.cyan(displayName)
|
|
175
|
+
: chalk.green(displayName)
|
|
176
|
+
|
|
177
|
+
const row: Record<string, string> = {
|
|
178
|
+
Name: coloredName,
|
|
179
|
+
Mode: chalk.gray(modeString),
|
|
180
|
+
Owner: directory.stats ? chalk.gray(getOwnerString(directory.stats)) : '',
|
|
181
|
+
Info: chalk.gray(linkInfo)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!isDevDirectory) {
|
|
185
|
+
row.Size = ''
|
|
186
|
+
row.Modified = directory.stats ? chalk.gray(getTimestampString(directory.stats.mtime)) : ''
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return row
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
const fileRows = files.sort((a, b) => a.name.localeCompare(b.name)).map(file => {
|
|
193
|
+
const displayName = file.linkTarget
|
|
194
|
+
? `${file.name} ${chalk.cyan('⟶')} ${file.linkTarget}`
|
|
195
|
+
: file.name
|
|
196
|
+
const modeStats = file.linkStats && file.linkStats.isSymbolicLink()
|
|
197
|
+
? file.linkStats
|
|
198
|
+
: file.stats
|
|
199
|
+
const modeString = modeStats
|
|
200
|
+
? getModeString(modeStats, file.linkStats?.isSymbolicLink() ? file.stats : undefined)
|
|
201
|
+
: ''
|
|
202
|
+
|
|
203
|
+
const modeType = modeString?.charAt(0) || ''
|
|
204
|
+
const coloredName = modeType === 'd' ? chalk.blue(displayName)
|
|
205
|
+
: modeType === 'l' ? chalk.cyan(displayName)
|
|
206
|
+
: chalk.green(displayName)
|
|
207
|
+
|
|
208
|
+
const info = (() => {
|
|
209
|
+
const linkInfo = getLinkInfo(file.linkTarget, file.linkStats, file.stats)
|
|
210
|
+
if (linkInfo) return linkInfo
|
|
211
|
+
|
|
212
|
+
if (descriptions.has(path.resolve(fullPath, file.name))) return descriptions.get(path.resolve(fullPath, file.name)) || ''
|
|
213
|
+
if (file.name.includes('.')) {
|
|
214
|
+
const ext = file.name.split('.').pop()
|
|
215
|
+
if (ext && descriptions.has('.' + ext)) return descriptions.get('.' + ext) || ''
|
|
216
|
+
}
|
|
217
|
+
if (!file.stats) return ''
|
|
218
|
+
if (file.stats.isBlockDevice() || file.stats.isCharacterDevice()) {
|
|
219
|
+
// TODO: zenfs `fs.mounts` is deprecated - use a better way of getting device info
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return ''
|
|
223
|
+
})()
|
|
224
|
+
|
|
225
|
+
const row: Record<string, string> = {
|
|
226
|
+
Name: coloredName,
|
|
227
|
+
Mode: chalk.gray(modeString),
|
|
228
|
+
Owner: file.stats ? chalk.gray(getOwnerString(file.stats)) : '',
|
|
229
|
+
Info: chalk.gray(info)
|
|
230
|
+
}
|
|
207
231
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
232
|
+
if (!isDevDirectory) {
|
|
233
|
+
row.Size = file.stats ? chalk.gray(humanFormat(file.stats.size)) : ''
|
|
234
|
+
row.Modified = file.stats ? chalk.gray(getTimestampString(file.stats.mtime)) : ''
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return row
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
const data = [...directoryRows, ...fileRows]
|
|
241
|
+
|
|
242
|
+
if (data.length > 0) {
|
|
243
|
+
const table = columnify(data, {
|
|
244
|
+
columns,
|
|
245
|
+
columnSplitter: ' ',
|
|
246
|
+
showHeaders: true,
|
|
247
|
+
headingTransform: (heading: string) => chalk.bold(heading)
|
|
211
248
|
})
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// Special output for certain directories
|
|
215
|
-
if (fullPath.startsWith('/dev')) data.forEach(row => row.splice(1, 2)) // remove size and modified columns
|
|
216
|
-
|
|
217
|
-
const columnWidths = data[0]?.map((_, colIndex) => Math.max(...data.map(row => {
|
|
218
|
-
// Remove ANSI escape sequences before calculating length
|
|
219
|
-
const cleanedCell = row[colIndex]?.replace(/\u001b\[.*?m/g, '')
|
|
220
|
-
// count all emojis as two characters
|
|
221
|
-
return cleanedCell?.length || 0
|
|
222
|
-
})))
|
|
223
|
-
|
|
224
|
-
for (const [rowIndex, row] of data.entries()) {
|
|
225
|
-
const line = row
|
|
226
|
-
.map((cell, index) => {
|
|
227
|
-
const paddedCell = cell.padEnd(columnWidths?.[index] ?? 0)
|
|
228
|
-
if (index === 0 && rowIndex > 0) {
|
|
229
|
-
if (row[3]?.startsWith('d')) return chalk.blue(paddedCell)
|
|
230
|
-
else if (row[3]?.startsWith('l')) return chalk.cyan(paddedCell)
|
|
231
|
-
else return chalk.green(paddedCell)
|
|
232
|
-
} else return rowIndex === 0 ? chalk.bold(paddedCell) : chalk.gray(paddedCell)
|
|
233
|
-
})
|
|
234
|
-
.join(' ')
|
|
235
|
-
|
|
236
|
-
if (data.length > 1) await writelnStdout(process, terminal, line)
|
|
249
|
+
|
|
250
|
+
await writelnStdout(process, terminal, table)
|
|
237
251
|
}
|
|
238
252
|
|
|
239
253
|
return 0
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal, User } from '@ecmaos/types'
|
|
3
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
4
|
+
import { writelnStdout, writelnStderr } from '../shared/helpers.js'
|
|
5
|
+
|
|
6
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
7
|
+
const usage = `Usage: user [COMMAND] [OPTIONS] [USERNAME]
|
|
8
|
+
Manage users on the system.
|
|
9
|
+
|
|
10
|
+
Commands:
|
|
11
|
+
add USERNAME Add a new user
|
|
12
|
+
del USERNAME Delete a user
|
|
13
|
+
mod USERNAME Modify a user
|
|
14
|
+
list List all users (default)
|
|
15
|
+
|
|
16
|
+
Options for 'add':
|
|
17
|
+
-m, --create-home Create home directory
|
|
18
|
+
-s, --shell SHELL Login shell (default: ecmaos)
|
|
19
|
+
-g, --gid GID Group ID (default: same as UID)
|
|
20
|
+
-u, --uid UID User ID (default: auto-assigned)
|
|
21
|
+
-p, --password PASS Password (will prompt if not provided)
|
|
22
|
+
|
|
23
|
+
Options for 'del':
|
|
24
|
+
-r, --remove-home Remove home directory
|
|
25
|
+
|
|
26
|
+
Options for 'mod':
|
|
27
|
+
-s, --shell SHELL Change login shell
|
|
28
|
+
-g, --gid GID Change group ID
|
|
29
|
+
-p, --password Change password (will prompt)
|
|
30
|
+
|
|
31
|
+
--help Display this help and exit`
|
|
32
|
+
writelnStdout(process, terminal, usage)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
36
|
+
return new TerminalCommand({
|
|
37
|
+
command: 'user',
|
|
38
|
+
description: 'Manage users on the system',
|
|
39
|
+
kernel,
|
|
40
|
+
shell,
|
|
41
|
+
terminal,
|
|
42
|
+
run: async (pid: number, argv: string[]) => {
|
|
43
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
44
|
+
|
|
45
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
46
|
+
printUsage(process, terminal)
|
|
47
|
+
return 0
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (shell.credentials.suid !== 0) {
|
|
51
|
+
await writelnStderr(process, terminal, chalk.red('user: permission denied'))
|
|
52
|
+
return 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const command = argv.length > 0 && argv[0] !== undefined && !argv[0].startsWith('-') ? argv[0] : 'list'
|
|
56
|
+
const remainingArgs = command !== 'list' ? argv.slice(1) : argv
|
|
57
|
+
|
|
58
|
+
switch (command) {
|
|
59
|
+
case 'list': {
|
|
60
|
+
const users = Array.from(kernel.users.all.values()) as User[]
|
|
61
|
+
|
|
62
|
+
if (users.length === 0) {
|
|
63
|
+
await writelnStdout(process, terminal, 'No users found')
|
|
64
|
+
return 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const uidWidth = Math.max(3, ...users.map(u => u.uid.toString().length))
|
|
68
|
+
const usernameWidth = Math.max(8, ...users.map(u => u.username.length))
|
|
69
|
+
const gidWidth = Math.max(3, ...users.map(u => u.gid.toString().length))
|
|
70
|
+
|
|
71
|
+
await writelnStdout(process, terminal, chalk.bold(
|
|
72
|
+
'UID'.padEnd(uidWidth) + '\t' +
|
|
73
|
+
'Username'.padEnd(usernameWidth) + '\t' +
|
|
74
|
+
'GID'.padEnd(gidWidth) + '\t' +
|
|
75
|
+
'Groups'
|
|
76
|
+
))
|
|
77
|
+
|
|
78
|
+
for (const usr of users) {
|
|
79
|
+
await writelnStdout(process, terminal,
|
|
80
|
+
chalk.yellow(usr.uid.toString().padEnd(uidWidth)) + '\t' +
|
|
81
|
+
chalk.green(usr.username.padEnd(usernameWidth)) + '\t' +
|
|
82
|
+
chalk.cyan(usr.gid.toString().padEnd(gidWidth)) + '\t' +
|
|
83
|
+
chalk.blue(usr.groups.join(', ') || '-')
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
case 'add': {
|
|
91
|
+
let username = ''
|
|
92
|
+
let createHome = false
|
|
93
|
+
let shellValue = 'ecmaos'
|
|
94
|
+
let gid: number | undefined
|
|
95
|
+
let uid: number | undefined
|
|
96
|
+
let password: string | undefined
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < remainingArgs.length; i++) {
|
|
99
|
+
const arg = remainingArgs[i]
|
|
100
|
+
if (!arg || typeof arg !== 'string') continue
|
|
101
|
+
if (arg.startsWith('-')) {
|
|
102
|
+
if (arg === '-m' || arg === '--create-home') {
|
|
103
|
+
createHome = true
|
|
104
|
+
} else if (arg === '-s' || arg === '--shell') {
|
|
105
|
+
if (i + 1 < remainingArgs.length) {
|
|
106
|
+
const nextArg = remainingArgs[++i]
|
|
107
|
+
if (nextArg) {
|
|
108
|
+
shellValue = nextArg
|
|
109
|
+
} else {
|
|
110
|
+
await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'s\''))
|
|
111
|
+
return 1
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'s\''))
|
|
115
|
+
return 1
|
|
116
|
+
}
|
|
117
|
+
} else if (arg === '-g' || arg === '--gid') {
|
|
118
|
+
if (i + 1 < remainingArgs.length) {
|
|
119
|
+
const gidStr = remainingArgs[++i]
|
|
120
|
+
if (gidStr) {
|
|
121
|
+
gid = parseInt(gidStr, 10)
|
|
122
|
+
if (isNaN(gid)) {
|
|
123
|
+
await writelnStderr(process, terminal, chalk.red(`user add: invalid GID '${gidStr}'`))
|
|
124
|
+
return 1
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'g\''))
|
|
128
|
+
return 1
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'g\''))
|
|
132
|
+
return 1
|
|
133
|
+
}
|
|
134
|
+
} else if (arg === '-u' || arg === '--uid') {
|
|
135
|
+
if (i + 1 < remainingArgs.length) {
|
|
136
|
+
const uidStr = remainingArgs[++i]
|
|
137
|
+
if (uidStr) {
|
|
138
|
+
uid = parseInt(uidStr, 10)
|
|
139
|
+
if (isNaN(uid)) {
|
|
140
|
+
await writelnStderr(process, terminal, chalk.red(`user add: invalid UID '${uidStr}'`))
|
|
141
|
+
return 1
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'u\''))
|
|
145
|
+
return 1
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'u\''))
|
|
149
|
+
return 1
|
|
150
|
+
}
|
|
151
|
+
} else if (arg === '-p' || arg === '--password') {
|
|
152
|
+
if (i + 1 < remainingArgs.length) {
|
|
153
|
+
const nextArg = remainingArgs[++i]
|
|
154
|
+
if (nextArg) {
|
|
155
|
+
password = nextArg
|
|
156
|
+
} else {
|
|
157
|
+
await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'p\''))
|
|
158
|
+
return 1
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'p\''))
|
|
162
|
+
return 1
|
|
163
|
+
}
|
|
164
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
165
|
+
printUsage(process, terminal)
|
|
166
|
+
return 0
|
|
167
|
+
} else {
|
|
168
|
+
await writelnStderr(process, terminal, chalk.red(`user add: invalid option -- '${arg.replace(/^-+/, '')}'`))
|
|
169
|
+
return 1
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
if (!username) {
|
|
173
|
+
username = arg
|
|
174
|
+
} else {
|
|
175
|
+
await writelnStderr(process, terminal, chalk.red(`user add: unexpected argument '${arg}'`))
|
|
176
|
+
return 1
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!username) {
|
|
182
|
+
await writelnStderr(process, terminal, chalk.red('user add: username required'))
|
|
183
|
+
await writelnStdout(process, terminal, 'Try \'user add --help\' for more information.')
|
|
184
|
+
return 1
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const allUsers = Array.from(kernel.users.all.values()) as User[]
|
|
188
|
+
if (allUsers.some((u: User) => u.username === username)) {
|
|
189
|
+
await writelnStderr(process, terminal, chalk.red(`user add: user '${username}' already exists`))
|
|
190
|
+
return 1
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (uid !== undefined && kernel.users.all.has(uid)) {
|
|
194
|
+
await writelnStderr(process, terminal, chalk.red(`user add: UID ${uid} already in use`))
|
|
195
|
+
return 1
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!password) {
|
|
199
|
+
password = await terminal.readline(chalk.cyan(`New password: `), true)
|
|
200
|
+
const confirm = await terminal.readline(chalk.cyan('Retype new password: '), true)
|
|
201
|
+
if (password !== confirm) {
|
|
202
|
+
await writelnStderr(process, terminal, chalk.red('user add: password mismatch'))
|
|
203
|
+
return 1
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
await kernel.users.add({
|
|
209
|
+
username,
|
|
210
|
+
password,
|
|
211
|
+
uid,
|
|
212
|
+
gid,
|
|
213
|
+
shell: shellValue,
|
|
214
|
+
home: `/home/${username}`
|
|
215
|
+
}, { noHome: !createHome })
|
|
216
|
+
await writelnStdout(process, terminal, chalk.green(`user add: user '${username}' created successfully`))
|
|
217
|
+
return 0
|
|
218
|
+
} catch (error) {
|
|
219
|
+
await writelnStderr(process, terminal, chalk.red(`user add: ${error instanceof Error ? error.message : 'Unknown error'}`))
|
|
220
|
+
return 1
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
case 'del': {
|
|
225
|
+
let username = ''
|
|
226
|
+
let removeHome = false
|
|
227
|
+
|
|
228
|
+
for (let i = 0; i < remainingArgs.length; i++) {
|
|
229
|
+
const arg = remainingArgs[i]
|
|
230
|
+
if (!arg || typeof arg !== 'string') continue
|
|
231
|
+
if (arg.startsWith('-')) {
|
|
232
|
+
if (arg === '-r' || arg === '--remove-home') {
|
|
233
|
+
removeHome = true
|
|
234
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
235
|
+
printUsage(process, terminal)
|
|
236
|
+
return 0
|
|
237
|
+
} else {
|
|
238
|
+
await writelnStderr(process, terminal, chalk.red(`user del: invalid option -- '${arg.replace(/^-+/, '')}'`))
|
|
239
|
+
return 1
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
if (!username) {
|
|
243
|
+
username = arg
|
|
244
|
+
} else {
|
|
245
|
+
await writelnStderr(process, terminal, chalk.red(`user del: unexpected argument '${arg}'`))
|
|
246
|
+
return 1
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!username) {
|
|
252
|
+
await writelnStderr(process, terminal, chalk.red('user del: username required'))
|
|
253
|
+
await writelnStdout(process, terminal, 'Try \'user del --help\' for more information.')
|
|
254
|
+
return 1
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const allUsers = Array.from(kernel.users.all.values()) as User[]
|
|
258
|
+
const usr = allUsers.find((u: User) => u.username === username)
|
|
259
|
+
if (!usr) {
|
|
260
|
+
await writelnStderr(process, terminal, chalk.red(`user del: user '${username}' does not exist`))
|
|
261
|
+
return 1
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (usr.uid === 0) {
|
|
265
|
+
await writelnStderr(process, terminal, chalk.red('user del: cannot delete root user'))
|
|
266
|
+
return 1
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
await kernel.users.remove(usr.uid)
|
|
271
|
+
|
|
272
|
+
if (removeHome && usr.home) {
|
|
273
|
+
try {
|
|
274
|
+
const removeDirRecursive = async (dirPath: string): Promise<void> => {
|
|
275
|
+
const entries = await shell.context.fs.promises.readdir(dirPath)
|
|
276
|
+
for (const entry of entries) {
|
|
277
|
+
const entryPath = `${dirPath}/${entry}`
|
|
278
|
+
const stat = await shell.context.fs.promises.stat(entryPath)
|
|
279
|
+
if (stat.isDirectory()) {
|
|
280
|
+
await removeDirRecursive(entryPath)
|
|
281
|
+
} else {
|
|
282
|
+
await shell.context.fs.promises.unlink(entryPath)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
await shell.context.fs.promises.rmdir(dirPath)
|
|
286
|
+
}
|
|
287
|
+
await removeDirRecursive(usr.home)
|
|
288
|
+
} catch {
|
|
289
|
+
await writelnStderr(process, terminal, chalk.yellow(`user del: warning: could not remove home directory '${usr.home}'`))
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
await shell.context.fs.promises.writeFile(
|
|
294
|
+
'/etc/passwd',
|
|
295
|
+
(await shell.context.fs.promises.readFile('/etc/passwd', 'utf8'))
|
|
296
|
+
.split('\n')
|
|
297
|
+
.filter((line: string) => !line.startsWith(`${username}:`))
|
|
298
|
+
.join('\n')
|
|
299
|
+
)
|
|
300
|
+
await shell.context.fs.promises.writeFile(
|
|
301
|
+
'/etc/shadow',
|
|
302
|
+
(await shell.context.fs.promises.readFile('/etc/shadow', 'utf8'))
|
|
303
|
+
.split('\n')
|
|
304
|
+
.filter((line: string) => !line.startsWith(`${username}:`))
|
|
305
|
+
.join('\n')
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
await writelnStdout(process, terminal, chalk.green(`user del: user '${username}' deleted successfully`))
|
|
309
|
+
return 0
|
|
310
|
+
} catch (error) {
|
|
311
|
+
await writelnStderr(process, terminal, chalk.red(`user del: ${error instanceof Error ? error.message : 'Unknown error'}`))
|
|
312
|
+
return 1
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
case 'mod': {
|
|
317
|
+
let username = ''
|
|
318
|
+
let shellValue: string | undefined
|
|
319
|
+
let gid: number | undefined
|
|
320
|
+
let changePassword = false
|
|
321
|
+
|
|
322
|
+
for (let i = 0; i < remainingArgs.length; i++) {
|
|
323
|
+
const arg = remainingArgs[i]
|
|
324
|
+
if (!arg || typeof arg !== 'string') continue
|
|
325
|
+
if (arg.startsWith('-')) {
|
|
326
|
+
if (arg === '-s' || arg === '--shell') {
|
|
327
|
+
if (i + 1 < remainingArgs.length) {
|
|
328
|
+
const nextArg = remainingArgs[++i]
|
|
329
|
+
if (nextArg) {
|
|
330
|
+
shellValue = nextArg
|
|
331
|
+
} else {
|
|
332
|
+
await writelnStderr(process, terminal, chalk.red('user mod: option requires an argument -- \'s\''))
|
|
333
|
+
return 1
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
await writelnStderr(process, terminal, chalk.red('user mod: option requires an argument -- \'s\''))
|
|
337
|
+
return 1
|
|
338
|
+
}
|
|
339
|
+
} else if (arg === '-g' || arg === '--gid') {
|
|
340
|
+
if (i + 1 < remainingArgs.length) {
|
|
341
|
+
const gidStr = remainingArgs[++i]
|
|
342
|
+
if (gidStr) {
|
|
343
|
+
gid = parseInt(gidStr, 10)
|
|
344
|
+
if (isNaN(gid)) {
|
|
345
|
+
await writelnStderr(process, terminal, chalk.red(`user mod: invalid GID '${gidStr}'`))
|
|
346
|
+
return 1
|
|
347
|
+
}
|
|
348
|
+
} else {
|
|
349
|
+
await writelnStderr(process, terminal, chalk.red('user mod: option requires an argument -- \'g\''))
|
|
350
|
+
return 1
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
await writelnStderr(process, terminal, chalk.red('user mod: option requires an argument -- \'g\''))
|
|
354
|
+
return 1
|
|
355
|
+
}
|
|
356
|
+
} else if (arg === '-p' || arg === '--password') {
|
|
357
|
+
changePassword = true
|
|
358
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
359
|
+
printUsage(process, terminal)
|
|
360
|
+
return 0
|
|
361
|
+
} else {
|
|
362
|
+
await writelnStderr(process, terminal, chalk.red(`user mod: invalid option -- '${arg.replace(/^-+/, '')}'`))
|
|
363
|
+
return 1
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
if (!username) {
|
|
367
|
+
username = arg
|
|
368
|
+
} else {
|
|
369
|
+
await writelnStderr(process, terminal, chalk.red(`user mod: unexpected argument '${arg}'`))
|
|
370
|
+
return 1
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (!username) {
|
|
376
|
+
await writelnStderr(process, terminal, chalk.red('user mod: username required'))
|
|
377
|
+
await writelnStdout(process, terminal, 'Try \'user mod --help\' for more information.')
|
|
378
|
+
return 1
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const allUsers = Array.from(kernel.users.all.values()) as User[]
|
|
382
|
+
const usr = allUsers.find((u: User) => u.username === username)
|
|
383
|
+
if (!usr) {
|
|
384
|
+
await writelnStderr(process, terminal, chalk.red(`user mod: user '${username}' does not exist`))
|
|
385
|
+
return 1
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const updates: Partial<User> = {}
|
|
389
|
+
if (shellValue !== undefined) {
|
|
390
|
+
updates.shell = shellValue
|
|
391
|
+
}
|
|
392
|
+
if (gid !== undefined) {
|
|
393
|
+
updates.gid = gid
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (changePassword) {
|
|
397
|
+
const newPassword = await terminal.readline(chalk.cyan('New password: '), true)
|
|
398
|
+
const confirm = await terminal.readline(chalk.cyan('Retype new password: '), true)
|
|
399
|
+
|
|
400
|
+
if (newPassword !== confirm) {
|
|
401
|
+
await writelnStderr(process, terminal, chalk.red('user mod: password mismatch'))
|
|
402
|
+
return 1
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
const hashedPassword = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(newPassword.trim()))
|
|
407
|
+
updates.password = Array.from(new Uint8Array(hashedPassword)).map(b => b.toString(16).padStart(2, '0')).join('')
|
|
408
|
+
} catch (error) {
|
|
409
|
+
await writelnStderr(process, terminal, chalk.red(`user mod: failed to hash password: ${error instanceof Error ? error.message : 'Unknown error'}`))
|
|
410
|
+
return 1
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (Object.keys(updates).length === 0 && !changePassword) {
|
|
415
|
+
await writelnStderr(process, terminal, chalk.red('user mod: no changes specified'))
|
|
416
|
+
return 1
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
await kernel.users.update(usr.uid, updates)
|
|
421
|
+
await writelnStdout(process, terminal, chalk.green(`user mod: user '${username}' modified successfully`))
|
|
422
|
+
return 0
|
|
423
|
+
} catch (error) {
|
|
424
|
+
await writelnStderr(process, terminal, chalk.red(`user mod: ${error instanceof Error ? error.message : 'Unknown error'}`))
|
|
425
|
+
return 1
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
default:
|
|
430
|
+
await writelnStderr(process, terminal, chalk.red(`user: invalid command '${command}'`))
|
|
431
|
+
await writelnStdout(process, terminal, 'Try \'user --help\' for more information.')
|
|
432
|
+
return 1
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
})
|
|
436
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -51,6 +51,7 @@ import { createCommand as createTest } from './commands/test.js'
|
|
|
51
51
|
import { createCommand as createTr } from './commands/tr.js'
|
|
52
52
|
import { createCommand as createTrue } from './commands/true.js'
|
|
53
53
|
import { createCommand as createUniq } from './commands/uniq.js'
|
|
54
|
+
import { createCommand as createUser } from './commands/user.js'
|
|
54
55
|
import { createCommand as createWc } from './commands/wc.js'
|
|
55
56
|
import { createCommand as createWhich } from './commands/which.js'
|
|
56
57
|
import { createCommand as createWhoami } from './commands/whoami.js'
|
|
@@ -86,6 +87,7 @@ export { createCommand as createSort } from './commands/sort.js'
|
|
|
86
87
|
export { createCommand as createTest } from './commands/test.js'
|
|
87
88
|
export { createCommand as createTr } from './commands/tr.js'
|
|
88
89
|
export { createCommand as createUniq } from './commands/uniq.js'
|
|
90
|
+
export { createCommand as createUser } from './commands/user.js'
|
|
89
91
|
export { createCommand as createWc } from './commands/wc.js'
|
|
90
92
|
export { createCommand as createWhich } from './commands/which.js'
|
|
91
93
|
export { createCommand as createSockets } from './commands/sockets.js'
|
|
@@ -140,6 +142,7 @@ export function createAllCommands(kernel: Kernel, shell: Shell, terminal: Termin
|
|
|
140
142
|
tr: createTr(kernel, shell, terminal),
|
|
141
143
|
true: createTrue(kernel, shell, terminal),
|
|
142
144
|
uniq: createUniq(kernel, shell, terminal),
|
|
145
|
+
user: createUser(kernel, shell, terminal),
|
|
143
146
|
wc: createWc(kernel, shell, terminal),
|
|
144
147
|
which: createWhich(kernel, shell, terminal),
|
|
145
148
|
whoami: createWhoami(kernel, shell, terminal)
|