diff --git a/src/pages/terminalBackup/index.js b/src/pages/terminalBackup/index.js new file mode 100644 index 000000000..7a90f8cf7 --- /dev/null +++ b/src/pages/terminalBackup/index.js @@ -0,0 +1,7 @@ +/** + * Terminal backup page + * @param {() => void} onclose + */ +export default function TerminalBackup(onclose) { + import("./terminalBackup").then((res) => res.default(onclose)); +} diff --git a/src/pages/terminalBackup/terminalBackup.js b/src/pages/terminalBackup/terminalBackup.js new file mode 100644 index 000000000..7c6a94844 --- /dev/null +++ b/src/pages/terminalBackup/terminalBackup.js @@ -0,0 +1,150 @@ +import settingsPage from "components/settingsPage"; +import toast from "components/toast"; +import alert from "dialogs/alert"; +import loader from "dialogs/loader"; +import FileBrowser from "pages/fileBrowser"; +import helpers from "utils/helpers"; + +export default function TerminalBackup(onclose) { + const backupSettings = { + alpineBase: true, + packages: true, + home: false, + public: false, + }; + + const items = [ + { + key: "alpineBase", + text: "Alpine", + checkbox: backupSettings.alpineBase, + info: strings["alpineBase info"] || "Include Alpine environment", + }, + /*{ + key: "packages", + text: strings["packages"] || "Packages", + checkbox: backupSettings.packages, + info: strings["packages info"] || "Include installed packages", + },*/ + { + key: "home", + text: strings["home"] || "Home", + checkbox: backupSettings.home, + info: + strings["home info"] || + "Include Alpine /home /root and /public directory", + }, + { + key: "backup", + text: strings["backup"], + chevron: false, + info: "Create backup with selected components", + }, + ]; + + const page = settingsPage(strings["backup"], items, callback, undefined, { + preserveOrder: true, + pageClassName: "detail-settings-page", + listClassName: "detail-settings-list", + infoAsDescription: true, + valueInTail: true, + }); + + const oldHide = page.hide; + page.hide = () => { + oldHide(); + onclose?.(); + }; + page.show(); + + function setPackagesTooltip() { + const packagesRow = document.querySelector('[data-key="packages"]'); + if (!packagesRow) return; + const input = packagesRow.querySelector(".input-checkbox input"); + if (input) { + input.disabled = !!backupSettings.alpineBase; + } + } + + function enforcePackageRule(value) { + if (backupSettings.alpineBase && value === false) { + toast( + strings["packages cannot be disabled when alpine base enabled"] || + "Packages cannot be disabled while Alpine base is enabled.", + ); + backupSettings.packages = true; + const pkgRow = document.querySelector('[data-key="packages"]'); + if (pkgRow) { + const checkbox = pkgRow.querySelector(".input-checkbox input"); + if (checkbox) checkbox.checked = true; + } + return true; + } + return false; + } + + async function performBackup() { + try { + const { url } = await FileBrowser("folder", strings["select folder"]); + + if (!url) return; + + loader.showTitleLoader( + strings["creating backup"] || "Creating backup...", + ); + + const backupPath = await Terminal.backup({ + alpineBase: backupSettings.alpineBase, + packages: backupSettings.packages, + home: backupSettings.home, + }); + + await system.copyToUri( + backupPath, + url, + "aterm_backup.tar", + console.log, + console.error, + ); + + loader.removeTitleLoader(); + alert( + strings.success.toUpperCase(), + `${strings["backup successful"] || "Backup successful"}.`, + ); + } catch (error) { + loader.removeTitleLoader(); + console.error("Terminal backup failed:", error); + toast(error.toString()); + } + } + + function callback(key, value) { + switch (key) { + case "alpineBase": + backupSettings.alpineBase = value; + if (value) { + backupSettings.packages = true; + const pkgRow = document.querySelector('[data-key="packages"]'); + if (pkgRow) { + const checkbox = pkgRow.querySelector(".input-checkbox input"); + if (checkbox) checkbox.checked = true; + } + } + setPackagesTooltip(); + break; + case "packages": + if (enforcePackageRule(value)) return; + backupSettings.packages = value; + break; + case "home": + backupSettings.home = value; + break; + case "backup": + performBackup(); + return; + default: + break; + } + } +} diff --git a/src/plugins/terminal/scripts/init-sandbox.sh b/src/plugins/terminal/scripts/init-sandbox.sh index 3b8a7c3d8..8b15ca0b4 100644 --- a/src/plugins/terminal/scripts/init-sandbox.sh +++ b/src/plugins/terminal/scripts/init-sandbox.sh @@ -62,7 +62,10 @@ ARGS="$ARGS -b /dev/urandom:/dev/random" ARGS="$ARGS -b /proc" ARGS="$ARGS -b /sys" ARGS="$ARGS -b $PREFIX" +#keeping /public for backward compatibility, as some users might be using it for storing files ARGS="$ARGS -b $PREFIX/public:/public" +ARGS="$ARGS -b $PREFIX/public:/home" +ARGS="$ARGS -b $PREFIX/public:/root" ARGS="$ARGS -b $PREFIX/alpine/tmp:/dev/shm" diff --git a/src/plugins/terminal/www/Terminal.js b/src/plugins/terminal/www/Terminal.js index 61512062c..51c9f3adc 100644 --- a/src/plugins/terminal/www/Terminal.js +++ b/src/plugins/terminal/www/Terminal.js @@ -321,25 +321,86 @@ const Terminal = { * console.error(`Backup failed: ${error}`); * } */ - backup() { + backup(options = {}) { return new Promise(async (resolve, reject) => { if (!await this.isInstalled()) { reject("Alpine is not installed."); return; } + + const opts = { + alpineBase: true, + packages: false, + home: false, + ...options, + }; + + const includeFiles = []; + const excludePaths = [ + "alpine/system", + "alpine/vendor", + "alpine/sdcard", + "alpine/storage", + "alpine/apex", + "alpine/odm", + "alpine/product", + "alpine/system_ext", + "alpine/linkerconfig", + "alpine/proc", + "alpine/sys", + "alpine/dev", + "alpine/run", + "alpine/tmp", + ]; + + if (opts.alpineBase) { + includeFiles.push("alpine", ".downloaded", ".extracted", ".configured", "axs"); + if (opts.packages) { + includeFiles.push("alpine/data"); + excludePaths.splice(excludePaths.indexOf("alpine/data"), 1); + } + if (opts.home) { + const checkCmd = `test -e "$PREFIX/public" && echo "exists" || echo "not"`; + const checkResult = await Executor.execute(checkCmd); + if (checkResult === "exists") { + includeFiles.push("public"); + } + } + } else { + // If alpineBase is disabled, only include public if home is enabled + if (opts.home) { + const checkCmd = `test -e "$PREFIX/public" && echo "exists" || echo "not"`; + const checkResult = await Executor.execute(checkCmd); + if (checkResult === "exists") { + includeFiles.push("public"); + } + } + } + + if (!includeFiles.length) { + reject("No components selected for backup."); + return; + } + + let excludeCmd = ""; + excludePaths.forEach((path) => { + excludeCmd += ` --exclude=${path}`; + }); + + const includeStr = includeFiles.join(" "); const cmd = ` set -e - INCLUDE_FILES="alpine .downloaded .extracted .configured axs" + INCLUDE_FILES="${includeStr}" if [ "$FDROID" = "true" ]; then INCLUDE_FILES="$INCLUDE_FILES libtalloc.so.2 libproot-xed.so" fi - EXCLUDE="--exclude=alpine/data --exclude=alpine/system --exclude=alpine/vendor --exclude=alpine/sdcard --exclude=alpine/storage --exclude=alpine/public --exclude=alpine/apex --exclude=alpine/odm --exclude=alpine/product --exclude=alpine/system_ext --exclude=alpine/linkerconfig --exclude=alpine/proc --exclude=alpine/sys --exclude=alpine/dev --exclude=alpine/run --exclude=alpine/tmp" - tar -cf "$PREFIX/aterm_backup.tar" -C "$PREFIX" $EXCLUDE $INCLUDE_FILES + tar -cf "$PREFIX/aterm_backup.tar" -C "$PREFIX"${excludeCmd} $INCLUDE_FILES echo "ok" `; + const result = await Executor.execute(cmd); if (result === "ok") { - resolve(cordova.file.dataDirectory + "aterm_backup.tar"); + resolve(cordova.file.dataDirectory + "usr/aterm_backup.tar"); } else { reject(result); } diff --git a/src/settings/terminalSettings.js b/src/settings/terminalSettings.js index 28abbf025..3651b26ca 100644 --- a/src/settings/terminalSettings.js +++ b/src/settings/terminalSettings.js @@ -11,6 +11,7 @@ import loader from "dialogs/loader"; import fonts from "lib/fonts"; import appSettings from "lib/settings"; import FileBrowser from "pages/fileBrowser"; +import TerminalBackup from "pages/terminalBackup"; import helpers from "utils/helpers"; export default function terminalSettings() { @@ -248,7 +249,7 @@ export default function terminalSettings() { return; case "backup": - terminalBackup(); + TerminalBackup(() => console.log("Backup page closed")); return; case "restore": @@ -291,74 +292,43 @@ export default function terminalSettings() { break; } } +} - /** - * Creates a backup of the terminal installation - */ - async function terminalBackup() { - try { - // Ask user to select backup location - const { url } = await FileBrowser("folder", strings["select folder"]); - - loader.showTitleLoader(); - - // Create backup - const backupPath = await Terminal.backup(); - await system.copyToUri( - backupPath, - url, - "aterm_backup.tar", - console.log, - console.error, - ); - loader.removeTitleLoader(); - alert(strings.success.toUpperCase(), `${strings["backup successful"]}.`); - } catch (error) { - loader.removeTitleLoader(); - console.error("Terminal backup failed:", error); - toast(error.toString()); - } - } - - /** - * Restores terminal installation - */ - async function terminalRestore() { - try { - await Executor.execute("rm -rf $PREFIX/aterm_backup.*"); +/** + * Restores terminal installation + */ +async function terminalRestore() { + try { + await Executor.execute("rm -rf $PREFIX/aterm_backup.*"); - sdcard.openDocumentFile( - async (data) => { - loader.showTitleLoader(); - //this will create a file at $PREFIX/atem_backup.tar.tar - await system.copyToUri( - data.uri, - cordova.file.dataDirectory, - "aterm_backup.tar", - console.log, - console.error, - ); + sdcard.openDocumentFile( + async (data) => { + loader.showTitleLoader(); + //this will create a file at $PREFIX/atem_backup.tar.tar + await system.copyToUri( + data.uri, + cordova.file.dataDirectory, + "aterm_backup.tar", + console.log, + console.error, + ); - // Restore - await Terminal.restore(); + // Restore + await Terminal.restore(); - //Cleanup restore file - await Executor.execute("rm -rf $PREFIX/aterm_backup.*"); + //Cleanup restore file + await Executor.execute("rm -rf $PREFIX/aterm_backup.*"); - loader.removeTitleLoader(); - alert( - strings.success.toUpperCase(), - "Terminal restored successfully", - ); - }, - toast, - "application/x-tar", - ); - } catch (error) { - loader.removeTitleLoader(); - console.error("Terminal restore failed:", error); - toast(error.toString()); - } + loader.removeTitleLoader(); + alert(strings.success.toUpperCase(), "Terminal restored successfully"); + }, + toast, + "application/x-tar", + ); + } catch (error) { + loader.removeTitleLoader(); + console.error("Terminal restore failed:", error); + toast(error.toString()); } }