1
0

fix: added ability for regenerate checksums with issued mr migrations.

This commit is contained in:
2025-07-10 21:09:06 +03:00
parent 1ef96c447e
commit 332a543f66
12 changed files with 349 additions and 141 deletions

View File

@@ -161,11 +161,11 @@ async function setupApp() {
initAnalytics()
if (!telemetry) {
console.info("[AR] Telemetry disabled by default (Hard patched).")
console.info("[AR] Telemetry disabled by default (Hard patched).")
optOutAnalytics()
}
if (!personalized_ads) {
console.info("[AR] Personalized ads disabled by default (Hard patched).")
console.info("[AR] Personalized ads disabled by default (Hard patched).")
}
if (dev) debugAnalytics()

View File

@@ -18,11 +18,15 @@ import { cancel_directory_change } from '@/helpers/settings.ts'
import { install } from '@/helpers/profile.js'
import { trackEvent } from '@/helpers/analytics'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { applyMigrationFix } from '@/helpers/utils.js'
const errorModal = ref()
const error = ref()
const closable = ref(true)
const errorCollapsed = ref(false)
const language = ref('en')
const migrationFixSuccess = ref(null) // null | true | false
const migrationFixCallbackModel = ref()
const title = ref('An error occurred')
const errorType = ref('unknown')
@@ -148,6 +152,25 @@ async function copyToClipboard(text) {
copied.value = false
}, 3000)
}
function toggleLanguage() {
language.value = language.value === 'en' ? 'ru' : 'en'
}
async function onApplyMigrationFix(eol) {
console.log(`[AR] • Attempting to apply migration ${eol.toUpperCase()} fix`)
try {
const result = await applyMigrationFix(eol)
migrationFixSuccess.value = result === true
console.log(`[AR] • Successfully applied migration ${eol.toUpperCase()} fix`, result)
} catch (err) {
console.error(`[AR] • Failed to apply migration fix:`, err)
migrationFixSuccess.value = false
} finally {
migrationFixCallbackModel.value?.show?.()
}
}
</script>
<template>
@@ -298,6 +321,11 @@ async function copyToClipboard(text) {
<template v-if="copied"> <CheckIcon class="text-green" /> Copied! </template>
<template v-else> <CopyIcon /> Copy debug info </template>
</button>
<ButtonStyled color="brand">
<a href="https://me.astralium.su/get/ar/help" target="_blank" rel="noopener noreferrer">
<button>Get AstralRinth support</button>
</a>
</ButtonStyled>
</ButtonStyled>
</div>
<template v-if="hasDebugInfo">
@@ -316,9 +344,116 @@ async function copyToClipboard(text) {
<pre class="m-0 px-4 py-3 bg-bg rounded-none">{{ debugInfo }}</pre>
</Collapsible>
</div>
<div class="notice">
<div class="flex justify-between items-center">
<h3 v-if="language === 'en'" class="notice__title"> Important Notice </h3>
<h3 v-if="language === 'ru'" class="notice__title"> Важное уведомление </h3>
<ButtonStyled>
<button @click="toggleLanguage">
{{ language === 'en' ? '📖 Русский' : '📖 English' }}
</button>
</ButtonStyled>
</div>
<p v-if="language === 'en'" class="notice__text">
We're experiencing an issue with our database migration system due to differences in how different operating systems handle line endings. This might cause problems with our app's functionality.
</p>
<p v-if="language === 'en'" class="notice__text">
<strong>What's happening?</strong> When we build our app, we use a system that checks the integrity of our database migrations. However, this system can get confused when it encounters different line endings (like CRLF vs LF) used by different operating systems. This can lead to errors and make our app unusable.
</p>
<p v-if="language === 'en'" class="notice__text">
<strong>Why is this happening?</strong> This issue is caused by a combination of factors, including different operating systems handling line endings differently, Git's line ending conversion settings, and our app's build process.
</p>
<p v-if="language === 'en'" class="notice__text">
<strong>What are we doing about it?</strong> We're working to resolve this issue and ensure that our app works smoothly for all users. In the meantime, we apologize for any inconvenience this might cause and appreciate your patience and understanding.
</p>
<p v-if="language === 'ru'" class="notice__text">
Мы сталкиваемся с проблемой в нашей системе миграции базы данных из-за различий в том, как разные операционные системы обрабатывают окончания строк. Это может вызвать проблемы с функциональностью нашего приложения.
</p>
<p v-if="language === 'ru'" class="notice__text">
<strong>Что происходит?</strong> Когда мы строим наше приложение, мы используем систему, которая проверяет целостность наших миграций базы данных. Однако эта система может сбиваться, когда сталкивается с различными окончаниями строк (например, CRLF против LF), используемыми разными операционными системами. Это может привести к ошибкам и сделать наше приложение неработоспособным.
</p>
<p v-if="language === 'ru'" class="notice__text">
<strong>Почему это происходит?</strong> Эта проблема вызвана сочетанием факторов, включая различную обработку окончаний строк разными операционными системами, настройки преобразования окончаний строк в Git и процесс сборки нашего приложения.
</p>
<p v-if="language === 'ru'" class="notice__text">
<strong>Что мы с этим делаем?</strong> Мы работаем над решением этой проблемы и обеспечением бесперебойной работы нашего приложения для всех пользователей. В это время мы извиняемся за возможные неудобства и благодарим вас за терпение и понимание.
</p>
</div>
<h2 class="text-lg font-bold text-contrast">
<template v-if="language === 'en'">Possible fix in real time:</template>
<template v-if="language === 'ru'">Возможное исправление в реальном времени:</template>
</h2>
<div class="flex justify-between">
<ul class="flex flex-col gap-3">
<li>
<ButtonStyled color="purple">
<button
:title="language === 'en'
? 'Convert all line endings in migration files to LF (Unix-style: \\n)'
: 'Преобразовать все окончания строк в файлах миграций в LF (Unix-стиль: \\n)'"
aria-label="LF"
@click="onApplyMigrationFix('lf')"
>
{{ language === 'en' ? 'Apply LF Migration Fix' : 'Применить исправление миграции LF' }}
</button>
</ButtonStyled>
</li>
<li>
<ButtonStyled color="purple">
<button
:title="language === 'en'
? 'Convert all line endings in migration files to CRLF (Windows-style: \\r\\n)'
: 'Преобразовать все окончания строк в файлах миграций в CRLF (Windows-стиль: \\r\\n)'"
aria-label="CRLF"
@click="onApplyMigrationFix('crlf')"
>
{{ language === 'en' ? 'Apply CRLF Migration Fix' : 'Применить исправление миграции CRLF' }}
</button>
</ButtonStyled>
</li>
</ul>
</div>
</template>
</div>
</ModalWrapper>
<ModalWrapper
ref="migrationFixCallbackModel"
:header="language === 'en'
? '💡 Migration fix report'
: '💡 Отчет об исправлении миграции'"
:closable="closable">
<div class="modal-body">
<h2 class="text-lg font-bold text-contrast space-y-2">
<template v-if="migrationFixSuccess === true">
<p class="flex items-center gap-2 text-green-600">
{{ language === 'en'
? 'The migration fix has been applied successfully. Please restart the launcher and try to log in to the game :)'
: 'Исправление миграции успешно применено. Пожалуйста, перезапустите лаунчер и попробуйте снова авторизоваться в игре :)' }}
</p>
<p class="mt-2 text-sm text-gray-600">
{{ language === 'en'
? 'If the problem persists, please try the other fix.'
: 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }}
</p>
</template>
<template v-else-if="migrationFixSuccess === false">
<p class="flex items-center gap-2 text-red-600">
{{ language === 'en'
? 'The migration fix failed or had no effect.'
: 'Исправление миграции не было успешно применено или не имело эффекта.' }}
</p>
<p class="mt-2 text-sm text-gray-600">
{{ language === 'en'
? 'If the problem persists, please try the other fix.'
: 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }}
</p>
</template>
</h2>
</div>
</ModalWrapper>
</template>
<style>

View File

@@ -10,11 +10,17 @@ export async function getOS() {
return await invoke('plugin:utils|get_os')
}
// [AR] Feature
export async function getArtifact(downloadurl, filename, ostype, autoupdatesupported) {
console.log('Downloading build', downloadurl, filename, ostype, autoupdatesupported)
return await invoke('plugin:utils|get_artifact', { downloadurl, filename, ostype, autoupdatesupported })
}
// [AR] Patch fix
export async function applyMigrationFix(eol) {
return await invoke('plugin:utils|apply_migration_fix', { eol })
}
export async function openPath(path) {
return await invoke('plugin:utils|open_path', { path })
}

View File

@@ -218,6 +218,7 @@ fn main() {
"utils",
InlinedPlugin::new()
.commands(&[
"apply_migration_fix",
"get_artifact",
"get_os",
"should_disable_mouseover",

View File

@@ -11,10 +11,12 @@ use dashmap::DashMap;
use std::path::{Path, PathBuf};
use theseus::prelude::canonicalize;
use url::Url;
use theseus::util::utils;
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
tauri::plugin::Builder::new("utils")
.invoke_handler(tauri::generate_handler![
apply_migration_fix,
get_artifact,
get_os,
should_disable_mouseover,
@@ -27,9 +29,17 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
.build()
}
/// [AR] Patch fix
#[tauri::command]
pub async fn apply_migration_fix(eol: &str) -> Result<bool> {
let result = utils::apply_migration_fix(eol).await?;
Ok(result)
}
/// [AR] Feature
#[tauri::command]
pub async fn get_artifact(downloadurl: &str, filename: &str, ostype: &str, autoupdatesupported: bool) -> Result<()> {
theseus::download::init_download(downloadurl, filename, ostype, autoupdatesupported).await;
let _ = utils::init_download(downloadurl, filename, ostype, autoupdatesupported).await;
Ok(())
}

View File

@@ -41,7 +41,7 @@
]
},
"productName": "AstralRinth App",
"version": "0.10.302",
"version": "0.10.303",
"mainBinaryName": "AstralRinth App",
"identifier": "AstralRinthApp",
"plugins": {

View File

@@ -1,55 +0,0 @@
use std::process::exit;
use reqwest;
use tokio::fs::File as AsyncFile;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
async fn download_file(download_url: &str, local_filename: &str, os_type: &str, auto_update_supported: bool) -> Result<(), Box<dyn std::error::Error>> {
let download_dir = dirs::download_dir().ok_or("[download_file] • Failed to determine download directory")?;
let full_path = download_dir.join(local_filename);
let response = reqwest::get(download_url).await?;
let bytes = response.bytes().await?;
let mut dest_file = AsyncFile::create(&full_path).await?;
dest_file.write_all(&bytes).await?;
println!("[download_file] • File downloaded to: {:?}", full_path);
if auto_update_supported {
let status;
if os_type.to_lowercase() == "Windows".to_lowercase() {
status = Command::new("explorer")
.arg(download_dir.display().to_string())
.status()
.await
.expect("[download_file] • Failed to open downloads folder");
} else if os_type.to_lowercase() == "MacOS".to_lowercase() {
status = Command::new("open")
.arg(full_path.to_str().unwrap_or_default())
.status()
.await
.expect("[download_file] • Failed to execute command");
} else {
status = Command::new(".")
.arg(full_path.to_str().unwrap_or_default())
.status()
.await
.expect("[download_file] • Failed to execute command");
}
if status.success() {
println!("[download_file] • File opened successfully!");
} else {
eprintln!("[download_file] • Failed to open the file. Exit code: {:?}", status.code());
}
}
Ok(())
}
pub async fn init_download(download_url: &str, local_filename: &str, os_type: &str, auto_update_supported: bool) {
println!("[init_download] • Initialize downloading from • {:?}", download_url);
println!("[init_download] • Save local file name • {:?}", local_filename);
if let Err(e) = download_file(download_url, local_filename, os_type, auto_update_supported).await {
eprintln!("[init_download] • An error occurred! Failed to download the file: {}", e);
} else {
println!("[init_download] • Code finishes without errors.");
exit(0)
}
}

View File

@@ -12,8 +12,8 @@ pub mod pack;
pub mod process;
pub mod profile;
pub mod settings;
pub mod update; // [AR] Feature
pub mod tags;
pub mod download; // AstralRinth
pub mod worlds;
pub mod data {

View File

@@ -0,0 +1,42 @@
use reqwest;
use tokio::fs::File as AsyncFile;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
pub(crate) async fn download_file(download_url: &str, local_filename: &str, os_type: &str, auto_update_supported: bool) -> Result<(), Box<dyn std::error::Error>> {
let download_dir = dirs::download_dir().ok_or("[AR] • Failed to determine download directory")?;
let full_path = download_dir.join(local_filename);
let response = reqwest::get(download_url).await?;
let bytes = response.bytes().await?;
let mut dest_file = AsyncFile::create(&full_path).await?;
dest_file.write_all(&bytes).await?;
println!("[AR] • File downloaded to: {:?}", full_path);
if auto_update_supported {
let status;
if os_type.to_lowercase() == "Windows".to_lowercase() {
status = Command::new("explorer")
.arg(download_dir.display().to_string())
.status()
.await
.expect("[AR] • Failed to open downloads folder");
} else if os_type.to_lowercase() == "MacOS".to_lowercase() {
status = Command::new("open")
.arg(full_path.to_str().unwrap_or_default())
.status()
.await
.expect("[AR] • Failed to execute command");
} else {
status = Command::new(".")
.arg(full_path.to_str().unwrap_or_default())
.status()
.await
.expect("[AR] • Failed to execute command");
}
if status.success() {
println!("[AR] • File opened successfully!");
} else {
eprintln!("[AR] • Failed to open the file. Exit code: {:?}", status.code());
}
}
Ok(())
}

View File

@@ -8,7 +8,7 @@ and launching Modrinth mod packs
#![deny(unused_must_use)]
#[macro_use]
mod util;
pub mod util; // [AR] Refactor
mod api;
mod config;

View File

@@ -1,18 +1,31 @@
use crate::state::DirectoryInfo;
use crate::ErrorKind;
use sqlx::sqlite::{
SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions,
};
use sqlx::{Pool, Sqlite};
use std::env;
use tokio::time::Instant;
use std::collections::HashMap;
use std::str::FromStr;
use std::time::Duration;
use tokio::time::Instant;
pub(crate) async fn connect() -> crate::Result<Pool<Sqlite>> {
let pool = connect_without_migrate().await?;
sqlx::migrate!().run(&pool).await?;
if let Err(err) = stale_data_cleanup(&pool).await {
tracing::warn!(
"Failed to clean up stale data from state database: {err}"
);
}
Ok(pool)
}
async fn connect_without_migrate() -> crate::Result<Pool<Sqlite>> {
let settings_dir = DirectoryInfo::get_initial_settings_dir().ok_or(
crate::ErrorKind::FSError(
"Could not find valid config dir".to_string(),
),
ErrorKind::FSError("Could not find valid config dir".to_string()),
)?;
if !settings_dir.exists() {
@@ -20,7 +33,6 @@ pub(crate) async fn connect() -> crate::Result<Pool<Sqlite>> {
}
let db_path = settings_dir.join("app.db");
let db_exists = db_path.exists();
let uri = format!("sqlite:{}", db_path.display());
let conn_options = SqliteConnectOptions::from_str(&uri)?
@@ -34,22 +46,6 @@ pub(crate) async fn connect() -> crate::Result<Pool<Sqlite>> {
.connect_with(conn_options)
.await?;
if db_exists {
fix_modrinth_issued_migrations(&pool).await?;
}
sqlx::migrate!().run(&pool).await?;
if !db_exists {
fix_modrinth_issued_migrations(&pool).await?;
}
if let Err(err) = stale_data_cleanup(&pool).await {
tracing::warn!(
"Failed to clean up stale data from state database: {err}"
);
}
Ok(pool)
}
@@ -75,72 +71,103 @@ async fn stale_data_cleanup(pool: &Pool<Sqlite>) -> crate::Result<()> {
Ok(())
}
/*
// Patch by AstralRinth - 08.07.2025
Problem files:
// Patched by AstralRinth
Problem files, view detailed information in .gitattributes:
/packages/app-lib/migrations/20240711194701_init.sql !eol
CRLF -> 4c47e326f16f2b1efca548076ce638d4c90dd610172fe48c47d6de9bc46ef1c5abeadfdea05041ddd72c3819fa10c040
LF -> e973512979feac07e415405291eefafc1ef0bd89454958ad66f5452c381db8679c20ffadab55194ecf6ba8ec4ca2db21
/packages/app-lib/migrations/20240813205023_drop-active-unique.sql !eol
CRLF -> 10f4a494df6fd791a093cc61401ecf3f9750fa6b97aa304ab06e29671e446586240910ffbf806f6ddc484a756770dde9
LF -> 5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206
/packages/app-lib/migrations/20240930001852_disable-personalized-ads.sql !eol
CRLF -> c8028ec3a2e61d15586e2f69ad6c6be5ac03b95918c2014cefb183ed6c254a52aad6f9ce98cda13ad545da3398574702
LF -> c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57
/packages/app-lib/migrations/20241222013857_feature-flags.sql !eol
CRLF -> f8c55065e2563fa4738976eb13a052ae4c28da8d33143185550f6e1cee394a3243b1dca090b3e8bc50a93a8286a78c09
LF -> c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704
*/
async fn fix_modrinth_issued_migrations(
pool: &Pool<Sqlite>,
) -> crate::Result<()> {
let arch = env::consts::ARCH;
let os = env::consts::OS;
pub(crate) async fn fix_version_hash(
eol: &str,
) -> crate::Result<bool> {
let started = Instant::now();
tracing::info!("Running on OS: {}, ARCH: {}", os, arch);
// Create connection to the database without migrations
let pool = connect_without_migrate().await?;
tracing::info!("⚙️ Patching Modrinth corrupted migration checksums using EOL standard: {eol}");
if os == "windows" && arch == "x86_64" {
tracing::warn!("🛑 Skipping migration checksum fix on Windows x86_64 (runtime-detected)");
return Ok(());
// validate EOL input
if eol != "lf" && eol != "crlf" {
return Ok(false);
}
let started = Instant::now();
tracing::info!("Fixing modrinth issued migrations");
sqlx::query(
// [eol][version] -> checksum
let checksums: HashMap<(&str, &str), &str> = HashMap::from([
(
("lf", "20240711194701"),
"e973512979feac07e415405291eefafc1ef0bd89454958ad66f5452c381db8679c20ffadab55194ecf6ba8ec4ca2db21",
),
(
("crlf", "20240711194701"),
"4c47e326f16f2b1efca548076ce638d4c90dd610172fe48c47d6de9bc46ef1c5abeadfdea05041ddd72c3819fa10c040",
),
(
("lf", "20240813205023"),
"5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206",
),
(
("crlf", "20240813205023"),
"10f4a494df6fd791a093cc61401ecf3f9750fa6b97aa304ab06e29671e446586240910ffbf806f6ddc484a756770dde9",
),
(
("lf", "20240930001852"),
"c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57",
),
(
("crlf", "20240930001852"),
"c8028ec3a2e61d15586e2f69ad6c6be5ac03b95918c2014cefb183ed6c254a52aad6f9ce98cda13ad545da3398574702",
),
(
("lf", "20241222013857"),
"c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704",
),
(
("crlf", "20241222013857"),
"f8c55065e2563fa4738976eb13a052ae4c28da8d33143185550f6e1cee394a3243b1dca090b3e8bc50a93a8286a78c09",
),
]);
let mut changed = false;
for ((eol_key, version), checksum) in checksums.iter() {
if *eol_key != eol {
continue;
}
tracing::info!(
"⏳ Patching checksum for migration {version} ({})",
eol.to_uppercase()
);
let result = sqlx::query(&format!(
r#"
UPDATE "_sqlx_migrations"
SET checksum = X'e973512979feac07e415405291eefafc1ef0bd89454958ad66f5452c381db8679c20ffadab55194ecf6ba8ec4ca2db21'
WHERE version = '20240711194701';
"#,
)
.execute(pool)
SET checksum = X'{checksum}'
WHERE version = '{version}';
"#
))
.execute(&pool)
.await?;
tracing::info!("⚙️ Fixed checksum for first migration");
sqlx::query(
r#"
UPDATE "_sqlx_migrations"
SET checksum = X'5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206'
WHERE version = '20240813205023';
"#,
)
.execute(pool)
.await?;
tracing::info!("⚙️ Fixed checksum for second migration");
sqlx::query(
r#"
UPDATE "_sqlx_migrations"
SET checksum = X'c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57'
WHERE version = '20240930001852';
"#,
)
.execute(pool)
.await?;
tracing::info!("⚙️ Fixed checksum for third migration");
sqlx::query(
r#"
UPDATE "_sqlx_migrations"
SET checksum = X'c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704'
WHERE version = '20241222013857';
"#,
)
.execute(pool)
.await?;
tracing::info!("⚙️ Fixed checksum for fourth migration");
let elapsed = started.elapsed();
if result.rows_affected() > 0 {
changed = true;
}
}
tracing::info!(
"Fixed all known Modrinth checksums for migrations in {:.2?}",
elapsed
"Checksum patching completed in {:.2?} (changes: {})",
started.elapsed(),
changed
);
Ok(())
Ok(changed)
}

View File

@@ -1,16 +1,20 @@
///
/// [AR] Feature
///
use crate::Result;
use crate::api::update;
use crate::state::db;
use serde::{Deserialize, Serialize};
use std::process;
use tokio::io;
/*
AstralRinth Utils
*/
const PACKAGE_JSON_CONTENT: &str =
// include_str!("../../../../apps/app-frontend/package.json");
include_str!("../../../../apps/app/tauri.conf.json");
#[derive(Serialize, Deserialize)]
pub struct Launcher {
pub version: String
pub version: String,
}
pub fn read_package_json() -> io::Result<Launcher> {
@@ -19,3 +23,41 @@ pub fn read_package_json() -> io::Result<Launcher> {
Ok(launcher)
}
pub async fn apply_migration_fix(eol: &str) -> Result<bool> {
tracing::info!("[AR] • Attempting to apply migration fix");
let patched = db::fix_version_hash(eol).await?;
if patched {
tracing::info!("[AR] • Successfully applied migration fix");
} else {
tracing::error!("[AR] • Failed to apply migration fix");
}
Ok(patched)
}
pub async fn init_download(
download_url: &str,
local_filename: &str,
os_type: &str,
auto_update_supported: bool,
) -> Result<()> {
println!("[AR] • Initialize downloading from • {:?}", download_url);
println!("[AR] • Save local file name • {:?}", local_filename);
if let Err(e) = update::download_file(
download_url,
local_filename,
os_type,
auto_update_supported,
)
.await
{
eprintln!(
"[AR] • An error occurred! Failed to download the file: {}",
e
);
} else {
println!("[AR] • Code finishes without errors.");
process::exit(0)
}
Ok(())
}