Skip to content
Closed
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
34 changes: 34 additions & 0 deletions src/plugins/terminal/www/Terminal.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,40 @@ const Terminal = {
* @returns {Promise<boolean>} - Returns true if installation completes with exit code 0, void if not installing
*/
async startAxs(installing = false, logger = console.log, err_logger = console.error,failsafe = false) {
// Guard against callers (core terminal UI, or third-party plugins such as
// AI copilots that shell out to run `npm`/`node`) invoking startAxs before
// the Alpine sandbox has ever been provisioned on this device. Without this
// check, init-sandbox.sh does not exist yet and the caller sees a confusing
// "No such file or directory" + unknown node/npm versions instead of the
// sandbox being set up automatically.
if (!this._provisioning && !(await this.isInstalled())) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Concurrent startup skips installation

If another caller invokes startAxs() while installation is in progress, the shared _provisioning flag makes it skip the installation check. It can then run the sandbox script before extraction or configuration finishes, causing startup to fail instead of waiting for the sandbox to be ready.

if (!installing) {
const message = "Linux sandbox is not installed yet. Open Terminal once (or let the plugin install it) before running commands.";
err_logger(message);
throw new Error(message);
}
Comment on lines +19 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 First-use plugin startup throws

If a plugin calls startAxs(false) on a fresh device, this branch throws instead of installing the sandbox. The terminal UI has a separate installation step, but plugins using this entry point cannot run their first command without arranging installation themselves, so the promised auto-provisioning does not cover them.


// install() finishes by calling startAxs(true, ...) itself to apply the
// final sandbox configuration, at a point where isInstalled() is still
// false (the .configured marker is written by init-sandbox.sh during
// that very call). The _provisioning flag prevents that internal call
// from re-entering install() and recursing forever.
logger("Linux sandbox not found, installing it first...");
this._provisioning = true;
let installed;
try {
installed = await this.install(logger, err_logger);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 First install runs twice

On a fresh device, the terminal UI calls install(), which calls startAxs(true) before the .configured marker exists. This branch then calls install() again. The second pass removes the first extraction and repeats setup; on F-Droid, it downloads the sandbox again too. First-time setup takes longer, uses more data, and has another chance to fail.

} finally {
this._provisioning = false;
}
if (!installed) {
const message = this.lastInstallError || "Failed to install the Linux sandbox required to run this command.";
err_logger(message);
return false;
}
return true;
}

const filesDir = await new Promise((resolve, reject) => {
system.getFilesDir(resolve, reject);
});
Expand Down
Loading