Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4445f73
feat: add portable mode detection and configuration handling
MiguVT Aug 14, 2025
c3b4b28
feat: add documentation for portable mode functionality and usage
MiguVT Aug 14, 2025
2337c05
Merge branch 'modrinth:main' into portable-PR-modrinth
MiguVT Aug 14, 2025
37e5c87
refactor: simplify config directory handling in portable mode
MiguVT Aug 14, 2025
b5887a5
Merge branch 'main' into portable-PR-modrinth
MiguVT Aug 14, 2025
d3bb85d
Merge branch 'modrinth:main' into portable-PR-modrinth
MiguVT Aug 15, 2025
98854eb
Improves portable mode handling
MiguVT Aug 15, 2025
42525c9
Consolidates portable mode detection
MiguVT Aug 15, 2025
6bbe5aa
Enhances portable mode support by adding environment variable checks …
MiguVT Aug 16, 2025
22f27ca
Refactors portable mode handling by consolidating environment variabl…
MiguVT Aug 16, 2025
d39c3b4
Refactors environment variable setup for macOS and Linux in portable …
MiguVT Aug 16, 2025
e1ca7e4
Adds Windows executable to the artifact upload step in the build work…
MiguVT Aug 16, 2025
e3086d8
Merge branch 'modrinth:main' into portable-PR-modrinth
MiguVT Aug 16, 2025
ef4dc7d
Made public DirectoryInfo in lib.rs
MiguVT Aug 16, 2025
1baacf3
Updated mod dirs to be public
MiguVT Aug 16, 2025
c8560af
Improves portable mode handling
MiguVT Aug 17, 2025
1990791
feat: add window state plugin conditionally based on portable mode
MiguVT Aug 17, 2025
4790620
feat: conditionally save window state based on portable mode
MiguVT Aug 17, 2025
e25f5f0
Improves portable mode detection
MiguVT Aug 17, 2025
03bbdec
Merge branch 'main' into portable-PR-modrinth
MiguVT Aug 17, 2025
53c9e3e
Merge branch 'main' into portable-PR-modrinth
MiguVT Aug 18, 2025
4fedce0
Merge branch 'main' into portable-PR-modrinth
MiguVT Aug 18, 2025
b2e9fc6
Merge branch 'main' into portable-PR-modrinth
MiguVT Aug 20, 2025
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
1 change: 1 addition & 0 deletions .github/workflows/theseus-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,4 @@ jobs:
target/universal-apple-darwin/release/bundle/dmg/Modrinth App_*.dmg*
target/release/bundle/nsis/Modrinth App_*-setup.exe*
target/release/bundle/nsis/Modrinth App_*-setup.nsis.zip*
target/release/Modrinth App.exe
11 changes: 10 additions & 1 deletion apps/app-frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,16 @@ initialize_state()
})

const handleClose = async () => {
await saveWindowState(StateFlags.ALL)
let isPortable = false
try {
isPortable = !!(await invoke('is_portable_mode'))
console.log('Portable mode:', isPortable)
} catch (err) {
console.warn('Failed to check portable mode:', err)
}
if (!isPortable) {
await saveWindowState(StateFlags.ALL)
}
await getCurrentWindow().close()
}

Expand Down
22 changes: 18 additions & 4 deletions apps/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use native_dialog::{DialogBuilder, MessageLevel};
use std::env;
use tauri::{Listener, Manager};
use theseus::prelude::*;
use theseus::DirectoryInfo;

mod api;
mod error;
Expand Down Expand Up @@ -141,9 +142,16 @@ fn restart_app(app: tauri::AppHandle) {
app.restart();
}

#[tauri::command]
fn is_portable_mode() -> bool {
theseus::DirectoryInfo::is_portable_mode()
}

// if Tauri app is called with arguments, then those arguments will be treated as commands
// ie: deep links or filepaths for .mrpacks
fn main() {
// Set up portable environment as early as possible (before any other initialization)
DirectoryInfo::setup_portable_env();
/*
tracing is set basd on the environment variable RUST_LOG=xxx, depending on the amount of logs to show
ERROR > WARN > INFO > DEBUG > TRACE
Expand Down Expand Up @@ -187,13 +195,18 @@ fn main() {
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_opener::init())
.plugin(
.plugin(tauri_plugin_opener::init());

// Only add window state plugin if not portable
if !theseus::DirectoryInfo::is_portable_mode() {
builder = builder.plugin(
tauri_plugin_window_state::Builder::default()
.with_filename("app-window-state.json")
.build(),
)
.setup(|app| {
);
}

builder = builder.setup(|app| {
#[cfg(target_os = "macos")]
{
let payload = macos::deep_link::get_or_init_payload(app);
Expand Down Expand Up @@ -267,6 +280,7 @@ fn main() {
toggle_decorations,
show_window,
restart_app,
is_portable_mode,
]);

tracing::info!("Initializing app...");
Expand Down
163 changes: 163 additions & 0 deletions apps/docs/src/content/docs/guide/portable-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
---
title: Portable Mode
description: Learn how to use Modrinth App in portable mode for maximum flexibility and convenience.
---

The Modrinth App supports **portable mode**, which allows you to run the application from any location without requiring installation to system directories. In portable mode, all application data is stored alongside the executable, making it perfect for USB drives, shared computers, or when you want a completely self-contained setup.

## What is Portable Mode?

Portable mode changes how the Modrinth App stores its data. Instead of using system directories like:

- `%APPDATA%\ModrinthApp` (Windows)
- `~/Library/Application Support/ModrinthApp` (macOS)
- `~/.local/share/ModrinthApp` (Linux)

All data is stored in a `ModrinthAppData` folder right next to the executable, making the entire installation truly portable.

## Benefits of Portable Mode

- **No Installation Required**: Run directly from any location
- **System Independence**: No registry entries or system directory modifications
- **Easy Backup**: Simply copy the entire folder to backup everything
- **Multi-System**: Use the same setup across different computers
- **Clean Removal**: Delete the folder to completely remove all traces
- **Isolation**: Perfect for testing without affecting main installations

## Enabling Portable Mode

To enable portable mode, simply create an empty file named `portable.txt` in the same directory as your Modrinth App executable, then launch the app. The app will automatically detect the file and enable portable mode. All app data will be stored in a `ModrinthAppData` folder next to the executable.

```
YourFolder/
├── Modrinth App.exe (or modrinth-app on Linux/macOS)
├── portable.txt ← Create this file
└── ModrinthAppData/ ← Created automatically on first run
```

## Directory Structure

When portable mode is enabled, your directory structure will look like this:

```
YourPortableFolder/
├── Modrinth App.exe # The application executable
├── portable.txt # Enables portable mode (Method 1)
└── ModrinthAppData/ # All app data stored here
├── profiles/ # Minecraft instances and profiles
│ ├── vanilla/
│ ├── modded/
│ └── ...
├── caches/ # Downloaded files cache
│ ├── mods/
│ ├── resource_packs/
│ └── ...
├── launcher_logs/ # Application logs
├── meta/ # Metadata files
└── app.db # Application database
```

## Creating a Portable Installation

### From Source (Developers)

If you're building from source:

1. Build the application:

```bash
pnpm app:build
```

2. Copy the executable from `target/release/` to your portable folder

3. Create the portable indicator:
```bash
# Create portable.txt file
echo "Portable mode enabled" > portable.txt
```

### From Release Binary

1. Download the Modrinth App executable from [modrinth.com/app](https://modrinth.com/app)
2. Place it in your desired portable folder
3. Create an empty `portable.txt` file next to the executable
4. Launch the app

## Use Cases

### USB Drive Setup

Perfect for carrying your complete Minecraft setup on a USB drive:

```
USB_Drive/
├── ModrinthApp/
│ ├── Modrinth App.exe
│ ├── portable.txt
│ └── ModrinthAppData/
└── other_files/
```

### Shared Computer

Use your personal setup on shared computers without affecting other users or requiring installation permissions.

### Testing Environment

Test different modrinth configurations or app versions without affecting your main installation.

### System Migration

Easily move your entire Minecraft setup to a new computer by copying the portable folder.

## Switching Between Modes

### Converting Regular Installation to Portable

1. Create a portable folder with the executable and `portable.txt`
2. Copy your existing data from the system directory to `ModrinthAppData/`
3. Launch the portable version

### Converting Portable to Regular Installation

1. Install the app normally
2. Copy data from `ModrinthAppData/` to the system directory
3. Remove the `portable.txt` file

## Technical Notes

- **Detection**: Portable mode is enabled only by the presence of `portable.txt` next to the executable
- **Config Directory**: The app sets the `THESEUS_CONFIG_DIR` environment variable automatically when portable mode is detected, ensuring all components use the correct data directory
- **Performance**: Portable mode has identical performance to regular installations
- **Platform Support**: Available on Windows, macOS, and Linux
- **First Launch**: The `ModrinthAppData` directory is created automatically on first run
- **File Permissions**: Ensure the portable folder has write permissions

## Troubleshooting

### App Not Detecting Portable Mode

- Verify `portable.txt` is in the same directory as the executable
- Check file permissions on the portable folder

### Data Not Saving

- Ensure the portable folder has write permissions
- Check that antivirus software isn't blocking file creation
- Verify there's sufficient disk space

### Performance Issues

- Portable mode shouldn't affect performance
- If using a USB drive, ensure it has adequate read/write speeds
- Consider using USB 3.0+ for better performance

## Security Considerations

- **Portable Storage**: Be mindful of where you store portable installations
- **Data Encryption**: Consider encrypting USB drives with sensitive data
- **Access Control**: Set appropriate file permissions on shared systems
- **Backup**: Regularly backup your portable installation

Portable mode makes the Modrinth App incredibly flexible and convenient for various use cases while maintaining all the functionality of a standard installation.
3 changes: 2 additions & 1 deletion packages/app-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ mod error;
mod event;
mod launcher;
mod logger;
mod state;
pub mod state;

pub use api::*;
pub use error::*;
Expand All @@ -25,3 +25,4 @@ pub use event::{
};
pub use logger::start_logger;
pub use state::State;
pub use state::dirs::DirectoryInfo;
61 changes: 60 additions & 1 deletion packages/app-lib/src/state/dirs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::state::{JavaVersion, Profile, Settings};
use crate::util::fetch::IoSemaphore;
use dashmap::DashSet;
use std::path::{Path, PathBuf};
use std::env;
use std::sync::Arc;
use tokio::fs;

Expand All @@ -20,9 +21,67 @@ pub struct DirectoryInfo {
}

impl DirectoryInfo {
/// Returns true if portable mode is enabled (portable.txt next to executable)
pub fn is_portable_mode() -> bool {
Self::portable_data_dir().is_some()
}

/// Call this as early as possible in main() to ensure all dependencies use the correct portable directory.
///
/// Example usage (at the very top of main.rs):
/// theseus::DirectoryInfo::setup_portable_env();
pub fn setup_portable_env() {
if let Some(portable_dir) = Self::portable_data_dir() {
Self::set_portable_env(&portable_dir);
}
}
/// Returns the path to the directory containing the running executable, if possible.
fn exe_dir() -> Option<PathBuf> {
std::env::current_exe().ok().and_then(|p| p.parent().map(|p| p.to_path_buf()))
}

/// Checks if portable mode is enabled (portable.txt) and returns the portable data dir if so.
fn portable_data_dir() -> Option<PathBuf> {
// portable.txt file next to executable
if let Some(exe_dir) = Self::exe_dir() {
let portable_txt = exe_dir.join("portable.txt");
if portable_txt.exists() {
return Some(exe_dir.join("ModrinthAppData"));
}
}
None
}

/// Sets the process environment variable for config dir to the portable dir if in portable mode.
fn set_portable_env(portable_dir: &Path) {
// Always set THESEUS_CONFIG_DIR for portable mode
unsafe {
env::set_var("THESEUS_CONFIG_DIR", portable_dir);
}
#[cfg(target_os = "windows")]
unsafe {
env::set_var("APPDATA", portable_dir);
}
#[cfg(target_os = "macos")]
unsafe {
// macOS typically uses HOME/Library/Application Support, but we can override XDG_DATA_HOME for some libs
env::set_var("XDG_DATA_HOME", portable_dir);
}
#[cfg(target_os = "linux")]
unsafe {
env::set_var("XDG_DATA_HOME", portable_dir);
}
}

// Get the settings directory
// init() is not needed for this function
pub fn get_initial_settings_dir() -> Option<PathBuf> {
// If portable mode, set env and use portable dir
if let Some(portable_dir) = Self::portable_data_dir() {
Self::set_portable_env(&portable_dir);
return Some(portable_dir);
}
// Otherwise, use THESEUS_CONFIG_DIR if set
Self::env_path("THESEUS_CONFIG_DIR")
.or_else(|| Some(dirs::data_dir()?.join("ModrinthApp")))
}
Expand Down Expand Up @@ -168,7 +227,7 @@ impl DirectoryInfo {
/// Get path from environment variable
#[inline]
fn env_path(name: &str) -> Option<PathBuf> {
std::env::var_os(name).map(PathBuf::from)
env::var_os(name).map(PathBuf::from)
}

#[tracing::instrument(skip(settings, exec, io_semaphore))]
Expand Down
2 changes: 1 addition & 1 deletion packages/app-lib/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::state::fs_watcher::FileWatcher;
use sqlx::SqlitePool;

// Submodules
mod dirs;
pub mod dirs;
pub use self::dirs::*;

mod profiles;
Expand Down