feat: terminal fail safe#2144
Conversation
Greptile SummaryThis PR adds a "failsafe mode" setting that launches the AXS terminal server directly via the system linker, bypassing proot — intended for devices where proot fails. To support this,
Confidence Score: 3/5The normal terminal path is broken and new Alpine installations will silently run in UTC instead of the device timezone. The --installing branch drops the ANDROID_TZ timezone setup that was the only mechanism for giving Alpine the correct local time, affecting all new installations. Combined with the normal-mode axs startup regression flagged in a prior thread, both core code paths have active defects. src/plugins/terminal/scripts/init-alpine.sh requires the most attention — the timezone removal and the removed axs startup are both in this file. src/plugins/terminal/www/Terminal.js also has open issues with PID tracking and unquoted paths in the failsafe command. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[createSession] --> B{isAxsRunning?}
B -- No --> C[getTerminalSettings]
C --> D{failsafeMode?}
D -- Yes --> E[startAxs with failsafeMode=true]
D -- No --> F[startAxs with failsafeMode=false]
E --> G["Executor.start sh\nchmod +x axs\necho $$ > pid\nlinker axs -c sh -i"]
F --> H["Executor.start sh\nsource init-sandbox.sh"]
H --> I["init-sandbox.sh to proot to init-alpine.sh"]
I --> J["exec /bin/bash --rcfile /initrc\naxs not started"]
G --> K["axs HTTP server running\nsh PID in pid file not axs PID"]
B -- Yes --> L[Poll up to 10s]
L --> M[createSession via HTTP]
K --> M
Reviews (2): Last reviewed commit: "feat: terminal fail safe" | Re-trigger Greptile |
| if [ "$1" = "--installing" ]; then | ||
| touch "$PREFIX/.configured" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Fallback to bash if no command specified | ||
| exec /bin/bash --rcfile /initrc |
There was a problem hiding this comment.
axs server and pid file no longer started in normal mode
The old init-alpine.sh wrote echo "$$" > "$PREFIX/pid" and then launched "$PREFIX/axs" -c "bash --rcfile /initrc -i" in the no-args (normal terminal start) branch. The new code replaces both with exec /bin/bash --rcfile /initrc, which runs bash inside proot but never starts the axs HTTP server.
createSession() in terminal.js connects to http://localhost:{port}/terminals — which only works when axs is running. With no pid file, isAxsRunning() always returns false, so startAxs is called on every session open; the 10-second polling loop exhausts, a toast fires, and then the fetch to the axs endpoint fails. The terminal is effectively broken in the default (non-failsafe) mode after this change.
| }).then(async (uuid) => { | ||
| await Executor.write(uuid, `source ${filesDir}/init-sandbox.sh ${installing ? "--installing" : ""}; exit`); | ||
| if (failsafeMode) { | ||
| await Executor.write(uuid, `chmod +x ${filesDir}/axs; echo $$ > ${filesDir}/pid; ${linker} ${filesDir}/axs -c "sh -i"; exit`); |
There was a problem hiding this comment.
Unquoted
${filesDir} will break on paths containing spaces. All three uses should be double-quoted within the shell string.
| await Executor.write(uuid, `chmod +x ${filesDir}/axs; echo $$ > ${filesDir}/pid; ${linker} ${filesDir}/axs -c "sh -i"; exit`); | |
| await Executor.write(uuid, `chmod +x "${filesDir}/axs"; echo $$ > "${filesDir}/pid"; ${linker} "${filesDir}/axs" -c "sh -i"; exit`); |
| if (failsafeMode) { | ||
| await Executor.write(uuid, `chmod +x ${filesDir}/axs; echo $$ > ${filesDir}/pid; ${linker} ${filesDir}/axs -c "sh -i"; exit`); | ||
| } else { |
There was a problem hiding this comment.
Failsafe mode: axs process is orphaned when
stopAxs() is called
echo $$ > ${filesDir}/pid stores the PID of the outer sh shell (started by Executor.start). stopAxs() then does kill -KILL $(cat $PREFIX/pid), which kills that outer shell. However, the ${linker} ${filesDir}/axs child process is not in the same process group and is not covered by --kill-on-exit (that flag is proot-specific). On Android/Linux, killing a parent with SIGKILL orphans its children — axs survives.
On the next createSession(), isAxsRunning() finds the old sh PID dead and returns false, triggering another startAxs. The new axs instance attempts to bind the same port while the old orphaned instance still holds it, causing the new start to fail silently. Users end up with leaked axs processes accumulating across terminal restarts.
Consider writing the axs PID directly — e.g., start axs in the background, capture $!, write that to the pid file, and wait for it — so stopAxs kills the process that actually owns the port.
| if (key === "failsafeMode") { | ||
| toast("Restart terminal to apply changes"); | ||
| } |
There was a problem hiding this comment.
Hardcoded English toast and missing
info-failsafeMode in non-English locales
toast("Restart terminal to apply changes") is not passed through the strings i18n map, so non-English users see English text. A corresponding key (e.g., "restart terminal to apply changes") should be added to all locale files and referenced via strings[...] here.
Additionally, every non-English locale file in this PR only received the "failsafe mode" key; none received "info-failsafeMode". The setting's info field will render as undefined for all non-English users.
|
Wow, Mini Termux-like. |
|
TODO: convert axs to a pie executable |
Closes #2084