Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions chromeos_startup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,8 @@ fi
# this is because ssh-keygen was introduced somewhere around R80, where many shims are still stuck on R73
# filesystem unfuck can only be done before stateful is mounted, which is perfectly fine in a shim but not if you run it while booted
# because mkfs is mean and refuses to let us format

# note that this will lead to confusing behaviour, since it will appear as if it crashed as a result of fakemurk

# startup plugins are also launched here, for low-level control over

# funny boot messages
echo "Oh fuck - ChromeOS is trying to kill itself." >/usr/share/chromeos-assets/text/boot_messages/en/block_devmode_virtual.txt
echo "ChromeOS detected developer mode and is trying to disable it to" >>/usr/share/chromeos-assets/text/boot_messages/en/block_devmode_virtual.txt
Expand Down
27 changes: 1 addition & 26 deletions cr50-update.conf
Original file line number Diff line number Diff line change
@@ -1,35 +1,10 @@
# Copyright 2016 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# This script first determines if it needs to run at all: if the cr50 firmware
# image is not present in the local directory this must be happening on a
# board without a cr50 device, no need to do anything.
#
# If the firmware image is present, the script checks the number of previous
# runs saved in a state file. The file name is bound to the firmware image, if
# the firmware image changes, the name of the state file will also have to
# change.
#
# In most cases one firmware update run will be enough, but sometimes more
# than one step will be required (when updating from an old cr50 version or
# when rotating RW keys). The entire chromebook needs to be restarted between
# cr50 update runs, up to four update runs on a particular firmware image are
# allowed by this script.
#
# The gsctool utility exit status indicates if more runs are required. Exit
# status of 0 means update has succeeded. Other exit statuses are processed by
# the follow up startup script cr50-result.conf.
#
# Gutted so that there's zero chance of the cr50 accidentally getting updated

description "Chromium OS startup file for cr50 firmware updater"
author "chromium-os-dev@chromium.org"

oom score -100

# Starts on boot-services by exception, since it triggers a long chain of
# dependant tpm-related daemons that need to start early. Normally services
# should start on 'starting system-services'.
start on started boot-services

script
Expand Down
2 changes: 2 additions & 0 deletions docs/dev_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ At the main menu, you have the option to enter a number corresponding to a menu
- `112`: Purge extension processes - Kills all extension processes.
- `113`: Prints a list of installed plugins and their individual metadata.
- `114` and `115`: Legacy install/uninstall plugin - Readline-based plugin management for easier interface from the helper extension.
- `2**`: Interface for the JS plugin filesystem access API (see `class MurkmodFsAccess`)
- `30*`: Interface for the JS plugin stack API (see `class MurkmodStack`)

The murkmod installation script (murkmod.sh, **not** the VT2 installer!) reads an environment variable called `MURKMOD_BRANCH` - it is normally set by option `26`, but it can be set by hand by using the following command:

Expand Down
33 changes: 33 additions & 0 deletions docs/js_plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,36 @@ Shit, that's a lot! Let's break it down a bit:
- `finished`: If this string is present in the output from the terminal, the task is considered completed. 99% of the time this should be `> (1-`, but depending on your use case ift can differ.
- `output_handler`: A function that takes a single string as a parameter, being the output from the terminal. Called every time output is received from the running task.
- `once_done`: A callback that is executed once the task is completed.

## Filesystem API

In order to asynchronously access the filesystem from a JS plugin, you can use `MurkmodFsAccess`:

```js
var fs = new MurkmodFsAccess();
```

The methods available in `MurkmodFsAccess` are as follows:

- `cd(string dir)`: Changes to a given directory.
- `ls()` or `ls(string dir)`: Lists contents of a directory as a string, delimited with spaces.
- `rm_dir(string dir)`: Deletes a directory recursively.
- `rm_file(string file)`: Deletes a single file.
- `mkdir(string dir)`: Creates a directory, recursively if neccesary.
- `touch(string file)`: Creates a file or updates its date last modified.
- `append(string file, string|array content)`: Appends a string or binary data to a given file.
- `write(string file, string|array content)`: Overwrites the contents of a file with the given string or binary data.
- `read(string file)`: Reads the contents of a given file.

All of these functions return a `Promise` which resolves differently according to each function.

For instance:

```js
var fs = new MurkmodFsAccess();
fs.cd("/").then(function(){
fs.read("startup_log").then(log => {
console.log("Startup log:", log);
});
});
```
49 changes: 49 additions & 0 deletions docs/plugin_communication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Plugin Communication

Plugins sometimes need to communicate with each other - and to do so, there is a stack-based communication system that exists that allows developers to easily communicate between both JS and bash plugins.

### In bash

First, ensure your plugin sources from `plugin_common.sh`:

```sh
. /usr/bin/murkmod_plugin_common.sh
```

Then, choose a name for your stack (for this guide, I'll go with `test_stack`):

```sh
starts "test_stack"
```

And finally, push a message to the stack that the JS plugin will pick up later:

```sh
pushs "Hello, world!"
```

### In JS

Create a new `MurkmodStack` object and set it to the same stack name as before:

```js
var stack = new MurkmodStack("test_stack");
```

In order to log out the message saved to the stack earlier, we can call:

```js
stack.pop().then(console.log); // Hello, world!
```

And we can push another message back:

```js
stack.push("Hello, bash plugin!");
```

And back in bash, we can read this again.

```bash
pops # Hello, bash plugin!
```
1 change: 1 addition & 0 deletions docs/plugin_dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ PLUGIN_FUNCTION="Print a hello world message"
PLUGIN_DESCRIPTION="The friendliest murkmod plugin you'll ever see."
PLUGIN_AUTHOR="rainestorme"
PLUGIN_VERSION=1
. /usr/bin/murkmod_plugin_common.sh
echo "Hello, World!"
```

Expand Down
12 changes: 8 additions & 4 deletions helper/html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>murkmod helper webui</title>
<link rel="stylesheet" href="../css/xterm.css" />
<script src="../js/xterm.js"></script>
<link rel="stylesheet" href="../css/lib/xterm.css" />
<script src="../js/lib/xterm.js"></script>
<link rel="stylesheet" href="https://fonts.xz.style/serve/inter.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css">
<link rel="stylesheet" href="../css/murkmod.css">
<link href="https://cdn.jsdelivr.net/npm/@sweetalert2/theme-dark@4/dark.css" rel="stylesheet">
<script src="../js/swal2.js"></script>
<script src="../js/lib/swal2.js"></script>
</head>
<body>
<h1>murkmod helper</h1>
Expand Down Expand Up @@ -104,6 +104,10 @@ <h1>murkmod helper</h1>
</div>
<div id="terminal-container"><div id="terminal-holder"><span id="title">xterm</span><span id="close">&times;</span><br><br><div id="terminal"></div></div></div>
<div id="terminal-bash-container"><div id="terminal-bash-holder"><span id="title-bash">murkmod helper terminal</span><span id="close-bash">&times;</span><br><br><div id="terminal-bash"></div></div></div>
<script src="../js/murkmod.js"></script>

<script src="../js/api/murkmod.js"></script>
<script src="../js/page/store.js"></script>
<script src="../js/page/interactivity.js"></script>
<script src="../js/api/fs.js"></script>
</body>
</html>
159 changes: 159 additions & 0 deletions helper/js/api/fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
class MurkmodFsAccess {
constructor() {
this.working_dir = "/";
this.mush_id = null;
this.listeners = [];
this.read_buffer = "";
this.read_start_tripped = false;
var self = this;
tp.openTerminalProcess("crosh", id => {
self.mush_id = id;
window.secondary_classes.push(self);
});
}

_generic_singlestep_promise(input, wait_for, response) {
var self = this;
return new Promise((resolve, reject) => {
tp.sendInput(self.mush_id, input);
self.listeners.push(function(decoded){
if (decoded.includes(wait_for)) {
tp.sendInput(self.mush_id, response);
} else if (decoded.includes("> (1-")) {
const index = self.listeners.indexOf(this);
if (index > -1) {
array.splice(index, 1);
}
resolve();
}
});
});
}

_generic_doublestep_promise(input, wait_for1, response1, wait_for2, response2) {
var self = this;
return new Promise((resolve, reject) => {
tp.sendInput(self.mush_id, input);
self.listeners.push(function(decoded){
if (decoded.includes(wait_for1)) {
tp.sendInput(self.mush_id, response1);
} else if (decoded.includes(wait_for2)) {
tp.sendInput(self.mush_id, response1);
} else if (decoded.includes("> (1-")) {
const index = self.listeners.indexOf(this);
if (index > -1) {
array.splice(index, 1);
}
resolve();
}
});
});
}

handle_output(decoded) {
for (var key in this.listeners) {
this.listeners[key](decoded);
}
}

cd(new_dir) {
var self = this;
return new Promise((resolve, reject) => {
tp.sendInput(self.mush_id, "209\n");
self.listeners.push(function(decoded){
if (decoded.includes("dir?")) {
tp.sendInput(self.mush_id, new_dir + "\n");
} else if (decoded.includes("> (1-")) {
self.working_dir = new_dir;
const index = self.listeners.indexOf(this);
if (index > -1) {
array.splice(index, 1);
}
resolve();
}
});
});
}

ls() {
return this._generic_singlestep_promise("208\n",
"dirname? (or . for current dir)",
".\n");
}

ls(dir) {
return this._generic_singlestep_promise("208\n",
"dirname? (or . for current dir)",
dir + "\n");
}

rm_dir(dir) {
return this._generic_singlestep_promise("207\n",
"dirname?",
dir + "\n");
}

rm_file(file) {
return this._generic_singlestep_promise("206\n",
"filename?",
file + "\n");
}

mkdir(dir) {
return this._generic_singlestep_promise("205\n",
"dirname?",
dir + "\n");
}

touch(file) {
return this._generic_singlestep_promise("204\n",
"filename?",
file + "\n");
}

append(file, content){
return this._generic_doublestep_promise("203\n",
"file to write to?",
file + "\n",
"base64 contents to append?",
btoa(content));
}

write(file, content) {
return this._generic_doublestep_promise("202\n",
"file to write to?",
file + "\n",
"base64 contents?",
btoa(content));
}

read(file) {
var self = this;
return new Promise((resolve, reject) => {
tp.sendInput(self.mush_id, input);
self.listeners.push(function(decoded){
if (self.read_start_tripped) {
self.read_buffer = self.read_buffer + decoded;
}
if (decoded.includes("file to read?")) {
tp.sendInput(self.mush_id, file+"\n");
}
if (decoded.includes("start content: ")) {
self.read_start_tripped = true;
}
if (self.read_buffer.includes("end content")) {
var startIndex = self.read_buffer.indexOf("start content: ") + "start content: ".length;
var endIndex = self.read_buffer.indexOf("end content");
var content = atob(self.read_buffer.substring(startIndex, endIndex));
self.read_buffer = "";
self.read_start_tripped = false;
const index = self.listeners.indexOf(this);
if (index > -1) {
array.splice(index, 1);
}
resolve(content);
}
});
});
}
}
Loading