Init rebase to Svelte and TS

This commit is contained in:
2025-11-08 09:43:14 +01:00
committed by Kamil Olszewski
parent 1af139201d
commit 4428cc7e8a
61 changed files with 12624 additions and 13 deletions

152
src/lib/stores/bash/bash.ts Normal file
View File

@@ -0,0 +1,152 @@
import { command } from '$app/server';
import { COMMANDS, GROUP, HELP_ARGS, PASSWD, type CommandArg, type ICommand } from './static';
import { VirtualFS } from './fs';
import { Terminal, type PrintData } from '../terminal';
import { Stack } from '../stack';
import path from 'path';
export interface Permission {
r: boolean;
w: boolean;
x: boolean;
}
export interface BashInitArgs {
stdio?: Terminal;
user: User;
fs: any;
}
// TODO: Finish this
export enum ExitCode {
SUCCESS = 0,
ERROR = 1
}
export interface User {
username: string;
passwd: string; //HASHED PASSWORD
uid: number; // Normal user 1000+ System user 1-999 root - 0
gid: number; // Primary group | 'Users' 1000 - Others - 1000+ root - 0
home: string;
history: string[];
cwd?: string[];
pwd?: string[];
}
export interface Group {
groupname: string;
gid: number; // Primary group 'Users' 1000 - Others - 1000+ root - 0
members: string[];
}
export class Bash {
private vfs: VirtualFS;
private _passwd: User[];
private _instances: Stack<User>;
private _group: Group[];
private _terminal!: Terminal;
private user: User;
private _helpArgs: CommandArg[];
private _commands: Record<string, ICommand>;
constructor(args: BashInitArgs) {
this.user = args.user;
this._helpArgs = HELP_ARGS;
this._commands = COMMANDS;
this._passwd = PASSWD;
this._group = GROUP;
this._terminal = args.stdio!;
this._instances = new Stack<User>();
this.vfs = new VirtualFS({ fs: args.fs, user: args.user });
console.log(this._commands);
}
updateHistory(input: string): void {
if ((this.user.history.length = 255)) {
this.user.history.unshift(...this.user.history.splice(-1));
this.user.history[0] = input;
} else {
this.user.history.push(input);
this.user.history.unshift(...this.user.history.splice(-1));
}
}
getCwd(): string[] {
return this.vfs.cwd;
}
getPwd(): string[] {
return this.vfs.pwd;
}
getUser(): User {
return this.user;
}
getFs(): VirtualFS {
return this.vfs;
}
changeUser(user: User) {
this.user = user;
this.vfs.home = this.vfs._splitPathString(user.home);
this.vfs.cwd = user.cwd ? user.cwd : this.vfs._splitPathString(user.home);
this.vfs.pwd = user.pwd ? user.pwd : this.vfs._splitPathString(user.home);
}
executeCommand(commandName: string, ...args: string[]): void {
const command = this._commands[commandName];
if (!command) this.throwError(ExitCode.ERROR);
if (command.root) {
if (this._group[1].members.includes(this.user.username)) {
let out: ExitCode = command.method.call(this, ...args);
this.throwError(out);
}
this.throwError(ExitCode.ERROR);
}
let out: ExitCode = command.method.call(this, ...args);
this.throwError(out);
}
throwError(code: ExitCode, data?: any): void {
//TODO: Make data some interface format or smh.
switch (code) {
default:
this.appendNewResult(this.vfs.pwd, 'Success!');
break;
}
}
userLogin(username: string, passwd: string): ExitCode {
const user: User | undefined = this._passwd.find((u) => u.username === username);
if (user === undefined) return ExitCode.ERROR;
if (user.passwd === passwd) {
this._instances.push(user);
this.changeUser(user);
return ExitCode.ERROR; //TODO: Make it return the exitcode of changeUser() if needed
} else return ExitCode.ERROR;
}
userLogout() {
this._instances.pop();
if (this._instances.size() === 0) {
//TODO: Implement system logout
} else {
this.changeUser(this._instances.peek()!);
}
}
appendNewResult(path: string[], output: any) {
const data: PrintData = {
path: this.vfs.formatPath(this.vfs.pathArrayToString(path)),
output: output
};
console.log('NEW RESULT - ', data);
this._terminal.PrintOutput(data);
}
}

130
src/lib/stores/bash/fs.ts Normal file
View File

@@ -0,0 +1,130 @@
import type { Permission, User } from './bash';
export enum Type {
Directory = 16384,
File = 32768
}
type NodePerms = {
user: Permission;
group: Permission;
other: Permission;
};
export interface FsInitArgs {
fs: any;
user: User;
}
export interface TreeNode {
name: string;
type: Type;
readonly: boolean;
interactible: boolean;
func: any;
children: TreeNode[];
content: string; // Path to the content of the file
link: string[]; // Symlink
permission: NodePerms;
owner: string;
group: string;
modtime: Date;
}
export class VirtualFS {
private root: TreeNode; // TODO make this the correct type
home: string[];
cwd: string[];
pwd: string[];
constructor(args: FsInitArgs) {
this.root = args.fs;
this.home = this._splitPathString(args.user.home);
this.cwd = args.user.cwd ? args.user.cwd : this.home;
this.pwd = args.user.pwd ? args.user.pwd : this.cwd;
console.log(this.home);
console.log(this.cwd);
console.log(this.pwd);
console.log('VFS INIT ', this._getNodeByPathArray(['/', 'home', 'kamil']));
}
_splitPathString(path: string): string[] {
if (path === '/') return ['/'];
const raw: string[] = path.split('/');
const parts: string[] = [];
for (let i = 0; i < raw.length; i++) {
if (raw[i].length > 0) parts.push(raw[i]);
}
return parts;
}
_isAbsolutePath = (path: string): boolean => {
return typeof path === 'string' && path.startsWith('/');
};
pathArrayToString(path: string[]): string {
if (path.length === 1 && path[0] === '/') return '/';
return '/' + path.join('/');
}
formatPath(path: string): string {
console.log('FORMAT PATH ', path);
const prefix = this.pathArrayToString(this.home);
if (path.startsWith(prefix)) {
return path.replace(prefix, '~');
} else return path;
}
resolvePath(path: string): string[] {
if (path === '' || path === undefined || path === null) return this.cwd.slice();
if (path.startsWith('/') && path.length === 1) return [];
if (path.startsWith('~')) {
const trail: string = path === '~' ? '' : path.slice(1);
const home: string = this.pathArrayToString(this.home);
path = home + (trail ? (trail.startsWith('/') ? '' : '/') + trail : '');
}
const start = this._isAbsolutePath(path) ? [] : this.cwd.slice();
console.log('START', start);
const parts = this._splitPathString(path);
console.log('PARTS', parts);
for (let i = 0; i < parts.length; i++) {
const seg = parts[i];
if (seg === '.' || seg === '') continue;
if (seg === '..') {
if (start.length > 1) start.pop();
continue;
}
start.push(seg);
}
if (start.length === 0) return [];
console.log('OUTPUT', start);
return start;
}
_getNodeByPathArray(path: string[]): TreeNode {
if (path.length === 1 && path[0] === '/') return this.root;
let node: TreeNode = this.root;
const parts: string[] = path.slice(path[0] === '/' ? 1 : 0);
for (let i = 0; i < parts.length; i++) {
const seg: string = parts[i];
if (node.type === Type.File) return node;
const newNode = node.children.find((child) => child.name === seg);
if (newNode !== undefined) node = newNode;
}
return node;
}
}

View File

@@ -0,0 +1,229 @@
import { Bash, ExitCode, type Group, type User } from './bash';
import { Type, type TreeNode } from './fs';
export type CommandArg = `-${string}`;
export interface ICommand {
method: (this: Bash, ...args: any[]) => ExitCode;
args: CommandArg[] | string[] | null;
help: string;
root: boolean;
}
export const GROUP: Group[] = [
{
groupname: 'sudo',
gid: 69,
members: ['root', 'admin']
},
{
groupname: 'users',
gid: 1000,
members: ['admin', 'user']
}
];
export const PASSWD: User[] = [
{
username: 'root',
passwd: '123',
uid: 0,
gid: 0,
home: '/',
history: [] //TODO: Delete this and declare a new history array when logging the user in.
},
{
username: 'admin',
passwd: '456',
uid: 1001,
gid: 1000,
home: '/home/admin',
history: [] //TODO: Delete this and declare a new history array when logging the user in.
},
{
username: 'user',
passwd: '789',
uid: 1002,
gid: 1000,
home: '/home/user',
history: [] //TODO: Delete this and declare a new history array when logging the user in.
}
];
export const HELP_ARGS: CommandArg[] = ['-h', '--help'];
export const cmd_return = function (this: Bash, ...args: string[]): ExitCode {
return 0;
};
export const cmd_cd = function (this: Bash, ...args: string[]): ExitCode {
const path = args[0];
let targetNode: TreeNode;
if (args.length > 1) return ExitCode.ERROR; // Too many args
// if no args cd into home dir
if (args.length === 0) {
this.getFs().cwd = this.getFs().home;
return ExitCode.SUCCESS;
}
// if the arg is - cd make your current dir the prev dir and vice versa
if (args[0] === '-') {
[this.getFs().cwd, this.getFs().pwd] = [this.getFs().pwd, this.getFs().cwd];
return ExitCode.SUCCESS;
}
// Change the input STRING path from relative to absolute by replacing ~ with the home directory path
//TODO: Change that to a global function inside fs class to parse all possible path formats????? already exists, need to verify
let resolvedPath = path.startsWith('~')
? path.replace('~', this.getFs().pathArrayToString(this.getFs().home))
: path;
this.getFs().pwd = this.getFs().cwd;
targetNode = this.getFs()._getNodeByPathArray(this.getFs().resolvePath(resolvedPath)); // Conversion from STRING path to ARRAY
if (!targetNode) return ExitCode.ERROR;
if (targetNode.type !== Type.Directory) return ExitCode.ERROR;
//if () return ExitCode.ERROR; // Check for read permissions on node and user
this.getFs().cwd = this.getFs().resolvePath(resolvedPath); // CD was successfull, change current dir to the verified target dir
return ExitCode.SUCCESS;
};
export const COMMANDS = {
return: {
method: cmd_return,
args: [] as CommandArg[],
help: 'PATH TO HELP.MD',
root: false
},
cd: {
method: cmd_cd,
args: [] as string[],
help: 'PATH TO HELP.MD',
root: false
}
} satisfies Record<string, ICommand>;
/* //export const commands {
return: {
method: this.cmd_return,
flags: [],
help: "Help about this command",
},
ls: {
method: this.cmd_ls,
flags: [],
help: "./help/ls.md",
},
echo: {
method: this.cmd_echo,
flags: [],
help: "",
},
touch: {
method: this.cmd_touch,
flags: [],
help: "",
},
mkdir: {
method: this.cmd_mkdir,
flags: [],
help: "",
},
pwd: {
method: this.cmd_pwd,
flags: [],
help: "",
},
cd: {
method: this.cmd_cd,
flags: [],
help: "",
},
exit: {
method: this.cmd_exit,
flags: [],
help: "",
},
cp: {
method: this.cmd_cp,
flags: [],
help: "",
},
mv: {
method: this.cmd_mv,
flags: [],
help: "",
},
rmdir: {
method: this.cmd_rmdir,
flags: [],
help: "",
},
cat: {
method: this.cmd_cat,
flags: [],
help: "",
},
dir: {
method: this.cmd_dir,
flags: [],
help: "",
},
less: {
method: this.cmd_less,
flags: [],
help: "",
},
chown: {
method: this.cmd_chown,
flags: [],
help: "",
},
chmod: {
method: this.cmd_chmod,
flags: [],
help: "",
},
reboot: {
method: this.cmd_reboot,
flags: [],
help: "",
},
help: {
method: this.cmd_help,
flags: [],
help: "",
},
whoami: {
method: this.cmd_whoami,
flags: [],
help: "",
},
rm: {
method: this.cmd_rm,
flags: [],
help: "",
},
sudo: {
method: this.cmd_sudo,
flags: [],
help: "",
},
su: {
method: this.cmd_su,
flags: [],
help: "",
},
clear: {
method: this.cmd_clear,
flags: [],
help: "",
}
} */