feat: portable build

This commit is contained in:
zhom
2026-03-29 14:55:20 +04:00
parent 1099459dbb
commit c516999f7a
7 changed files with 105 additions and 2 deletions
+39 -1
View File
@@ -225,6 +225,44 @@ jobs:
prerelease: false
args: ${{ matrix.args }}
- name: Create portable Windows ZIP
if: matrix.platform == 'windows-latest'
shell: bash
env:
TAG: ${{ github.ref_name }}
run: |
VERSION="${TAG#v}"
PORTABLE_DIR="Donut-Portable"
mkdir -p "$PORTABLE_DIR"
# Copy main executable
cp "src-tauri/target/${{ matrix.target }}/release/Donut.exe" "$PORTABLE_DIR/"
# Copy sidecar binaries
cp "src-tauri/target/${{ matrix.target }}/release/donut-proxy.exe" "$PORTABLE_DIR/"
cp "src-tauri/target/${{ matrix.target }}/release/donut-daemon.exe" "$PORTABLE_DIR/"
# Copy WebView2Loader if present
if [ -f "src-tauri/target/${{ matrix.target }}/release/WebView2Loader.dll" ]; then
cp "src-tauri/target/${{ matrix.target }}/release/WebView2Loader.dll" "$PORTABLE_DIR/"
fi
# Create .portable marker
touch "$PORTABLE_DIR/.portable"
# Create ZIP
7z a "Donut_${VERSION}_x64-portable.zip" "$PORTABLE_DIR"
- name: Upload portable ZIP to release
if: matrix.platform == 'windows-latest'
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: |
VERSION="${TAG#v}"
gh release upload "$TAG" "Donut_${VERSION}_x64-portable.zip" --clobber
- name: Clean up Apple certificate
if: matrix.platform == 'macos-latest' && always()
run: |
@@ -346,7 +384,7 @@ jobs:
### Windows
[Download Windows Installer (x64)](${BASE}/Donut_${VERSION}_x64-setup.exe)
[Download Windows Installer (x64)](${BASE}/Donut_${VERSION}_x64-setup.exe) · [Portable (x64)](${BASE}/Donut_${VERSION}_x64-portable.zip)
### Linux
+28
View File
@@ -235,6 +235,34 @@ jobs:
prerelease: true
args: ${{ matrix.args }}
- name: Create portable Windows ZIP
if: matrix.platform == 'windows-latest'
shell: bash
run: |
PORTABLE_DIR="Donut-Portable"
mkdir -p "$PORTABLE_DIR"
cp "src-tauri/target/${{ matrix.target }}/release/Donut.exe" "$PORTABLE_DIR/"
cp "src-tauri/target/${{ matrix.target }}/release/donut-proxy.exe" "$PORTABLE_DIR/"
cp "src-tauri/target/${{ matrix.target }}/release/donut-daemon.exe" "$PORTABLE_DIR/"
if [ -f "src-tauri/target/${{ matrix.target }}/release/WebView2Loader.dll" ]; then
cp "src-tauri/target/${{ matrix.target }}/release/WebView2Loader.dll" "$PORTABLE_DIR/"
fi
touch "$PORTABLE_DIR/.portable"
7z a "Donut_x64-portable.zip" "$PORTABLE_DIR"
- name: Upload portable ZIP to release
if: matrix.platform == 'windows-latest'
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NIGHTLY_TAG: "nightly-${{ steps.timestamp.outputs.timestamp }}"
run: |
gh release upload "$NIGHTLY_TAG" "Donut_x64-portable.zip" --clobber
- name: Clean up Apple certificate
if: matrix.platform == 'macos-latest' && always()
run: |
+4
View File
@@ -1604,6 +1604,10 @@ rm "{}"
#[tauri::command]
pub async fn check_for_app_updates() -> Result<Option<AppUpdateInfo>, String> {
if crate::app_dirs::is_portable() {
log::info!("App auto-updates disabled in portable mode");
return Ok(None);
}
// The disable_auto_updates setting controls app self-updates only
let disabled = crate::settings_manager::SettingsManager::instance()
.load_settings()
+26
View File
@@ -3,11 +3,29 @@ use std::path::PathBuf;
use std::sync::OnceLock;
static BASE_DIRS: OnceLock<BaseDirs> = OnceLock::new();
static PORTABLE_DIR: OnceLock<Option<PathBuf>> = OnceLock::new();
fn base_dirs() -> &'static BaseDirs {
BASE_DIRS.get_or_init(|| BaseDirs::new().expect("Failed to get base directories"))
}
/// Returns the portable base directory if a `.portable` marker exists next to the executable.
fn portable_dir() -> Option<&'static PathBuf> {
PORTABLE_DIR
.get_or_init(|| {
std::env::current_exe()
.ok()
.and_then(|exe| exe.parent().map(|p| p.to_path_buf()))
.filter(|dir| dir.join(".portable").exists())
})
.as_ref()
}
/// Returns true if the app is running in portable mode.
pub fn is_portable() -> bool {
portable_dir().is_some()
}
pub fn app_name() -> &'static str {
if cfg!(debug_assertions) {
"DonutBrowserDev"
@@ -28,6 +46,10 @@ pub fn data_dir() -> PathBuf {
return PathBuf::from(dir);
}
if let Some(dir) = portable_dir() {
return dir.join("data");
}
base_dirs().data_local_dir().join(app_name())
}
@@ -43,6 +65,10 @@ pub fn cache_dir() -> PathBuf {
return PathBuf::from(dir);
}
if let Some(dir) = portable_dir() {
return dir.join("cache");
}
base_dirs().cache_dir().join(app_name())
}
+3
View File
@@ -340,6 +340,9 @@ pub fn is_autostart_enabled() -> bool {
}
pub fn get_data_dir() -> Option<PathBuf> {
if crate::app_dirs::is_portable() {
return Some(crate::app_dirs::data_dir());
}
if let Some(proj_dirs) = ProjectDirs::from("com", "donutbrowser", "Donut Browser") {
Some(proj_dirs.data_dir().to_path_buf())
} else {
+2
View File
@@ -950,6 +950,7 @@ pub struct SystemInfo {
pub app_version: String,
pub os: String,
pub arch: String,
pub portable: bool,
}
#[tauri::command]
@@ -976,6 +977,7 @@ pub fn get_system_info() -> SystemInfo {
app_version: crate::app_auto_updater::AppAutoUpdater::get_current_version(),
os: os.to_string(),
arch: arch.to_string(),
portable: crate::app_dirs::is_portable(),
}
}
+3 -1
View File
@@ -128,6 +128,7 @@ export function SettingsDialog({
app_version: string;
os: string;
arch: string;
portable: boolean;
} | null>(null);
const { t } = useTranslation();
@@ -237,6 +238,7 @@ export function SettingsDialog({
app_version: string;
os: string;
arch: string;
portable: boolean;
}>("get_system_info");
setSystemInfo(info);
} catch {
@@ -1109,7 +1111,7 @@ export function SettingsDialog({
{systemInfo && (
<div className="pt-2 border-t">
<p className="text-xs text-muted-foreground font-mono whitespace-pre-line select-all">
{`Donut Browser ${systemInfo.app_version}\n${systemInfo.os} ${systemInfo.arch}`}
{`Donut Browser ${systemInfo.app_version}\n${systemInfo.os} ${systemInfo.arch}${systemInfo.portable ? " (portable)" : ""}`}
</p>
</div>
)}