كيف توزع تطبيق سطح المكتب الذي طورته بمفردك دون تحذيرات أمنية
TuBrief 편집팀
2026년 7월 15일
0
Computing/Software원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
بمجرد الانتهاء من تطوير تطبيق سطح المكتب الذي يعمل بشكل جيد على جهازك المحلي، تكون قد قطعت نصف الطريق فقط. تبدأ العقبة الحقيقية في اللحظة التي تظهر فيها نافذة تحذير حمراء على شاشة المستخدم بعد الضغط على رابط التحميل، مثل "ملف تالف" (Damaged file) أو "تم حماية جهاز الكمبيوتر الخاص بك" (Windows protected your PC).
إن تجاوز حواجز أمان نظام التشغيل، والتحكم في سرعة البناء (Build)، وبناء نظام تحديث تلقائي يضمن ترقية التطبيق إلى أحدث إصدار في كل مرة يفتحه المستخدم، أمر مرهق أكثر مما قد يبدو. حتى لو اخترت Tauri v2 لكونه أخف من Electron، تظل المشكلات الواقعية لعملية التوزيع قائمة. لقد لخصت هنا كيفية قيام المطورين الأفراد أو الفرق الصغيرة بتوزيع منتجاتهم بشكل نظيف دون إهدار تكاليف ووقت غير ضروريين.
تطبيقات سطح المكتب التي لا تحمل "توقيع كود" (Code Signing) يتم التعامل معها كبرامج ضارة على مستوى نظام التشغيل. لتجنب ظهور تحذيرات أمنية على جهاز المستخدم، تحتاج إلى استثمار بعض المال والقيام ببعض الأعمال الورقية.
لتوزيع تطبيقات macOS، يعد الاشتراك في برنامج مطوري Apple (Apple Developer Program) بتكلفة 99 دولاراً سنوياً أمراً إلزامياً. بمجرد حصولك على الحساب، يجب عليك إنشاء ملف src-tauri/Entitlements.plist الذي يحدد أذونات استثناء أمان الذاكرة حتى يعمل Tauri WebView بشكل صحيح. إذا تم حذف هذا الإعداد، سيتعطل التطبيق فور تشغيله.
`xml
<?xml version="1.0" encoding="UTF-8">
com.apple.security.cs.allow-jit
com.apple.security.cs.allow-unsigned-executable-memory
`
قم بتعيين هذا الملف في خيار الحزمة (bundle) داخل src-tauri/tauri.conf.json.
`json
{
"bundle": {
"macOS": {
"signingIdentity": "Developer ID Application: Your Name (TEAMID)",
"entitlements": "./Entitlements.plist",
"minimumSystemVersion": "11.0",
"dmg": {
"appPosition": { "x": 180, "y": 170 },
"applicationFolderPosition": { "x": 480, "y": 170 }
}
}
}
}
`
لاجتياز فلاتر SmartScreen في Windows، كان من الضروري سابقاً الحصول على شهادة التحقق الموسع (EV) بتكلفة تتراوح بين 400 إلى 700 دولار سنوياً على شكل رمز USB فعلي. هذا ليس مكلفاً فحسب، بل إنه مرهق للغاية للمطور الفردي.
البديل هو Azure Trusted Signing (ATS)، وهي خدمة توقيع سحابية من مايكروسوفت. من خلال دفع اشتراك شهري يبلغ حوالي 9.99 دولار، تتم معالجة التوقيع داخل سحابة HSM التي تديرها مايكروسوفت، لذا لا داعي للاحتفاظ بمفتاح فعلي.
AZURE_TENANT_ID, CLIENT_ID, CLIENT_SECRET) ومعلومات ATS في متغيرات بيئة GitHub Actions (Secrets).sign-tool أثناء عملية البناء لتطبيق التوقيع الرقمي على ملفات MSI أو EXE التي تم تجميعها بواسطة Tauri.التطبيقات الموقعة بهذه الطريقة تتجنب تحذيرات Windows SmartScreen منذ لحظة التحميل الأولى، مما يساعدك في الحفاظ على المستخدمين الذين قد يغادرون أثناء مرحلة التثبيت.
على الرغم من أن Tauri خفيف، إلا أن عملية البناء تتطلب تشغيل مترجم Rust وسلسلة أدوات النظام لكل نظام تشغيل بالكامل. ليس من غير المعتاد حدوث أخطاء في الربط (Linker errors) على أجهزة أعضاء الفريق الآخرين، أو فساد ملفات التوزيع بسبب تلوث التبعيات في البيئة المحلية. يجب إجراء عمليات بناء التوزيع دائماً داخل مسار CI/CD معزول.
المشكلة هي أن أدوات التشغيل الافتراضية (Hosted Runners) في GitHub Actions لا تمتلك المواصفات الكافية لبناء Rust. إذا كنت تقوم بتحميل التبعيات والبناء من الصفر في كل مرة، فمن المحتمل أن يستغرق بناء الإصدار أكثر من 10 دقائق.
في هذه الحالة، بدلاً من استخدام actions/cache البسيط لضغط الملفات وإرسالها إلى السحابة، فإن دمج إضافة ذاكرة تخزين مؤقت مخصصة مثل (swatinem/rust-cache) التي تعمل مع تخزين NVMe عالي الأداء أو أدوات تشغيل مستضافة مخصصة (مثل Namespace أو Depot) سيغير النتائج تماماً.
بناءً على سجلات بناء مشروع مشغل الموسيقى مفتوح المصدر spotify-player، فإن مقارنة الأداء بين أداة تشغيل GitHub العادية وأداة تشغيل مخصصة مع ذاكرة تخزين مؤقت للمجلد المحلي هي كما يلي:
| المنصة وتكوين الذاكرة | وقت أداة GitHub العادية | الوقت عند تطبيق تحسين الذاكرة | نسبة تقليل وقت البناء |
|---|---|---|---|
| Ubuntu Linux | 9 دقائق 31 ثانية | 34 ثانية | تقليل بنسبة 94.0% |
| macOS Darwin | 9 دقائق 31 ثانية | 27 ثانية | تقليل بنسبة 95.2% |
| Windows MSVC | 9 دقائق 31 ثانية | 44 ثانية | تقليل بنسبة 92.2% |
| تكلفة سير العمل | $0.44 لكل تشغيل | $0.074 لكل تشغيل | توفير بنسبة 83.1% |
مجرد ربط بنية تحتية لذاكرة التخزين المؤقت التي يتم الاحتفاظ بها بشكل دائم يقلل من وقت انتظار فريق التطوير بنسبة لا تقل عن 40%.
قم بتعيين إعدادات أتمتة التوزيع في ملف .github/workflows/publish.yml كما يلي:
`yaml
jobs:
build-binaries:
strategy:
matrix:
platform: [macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
# ... بعد تنفيذ خطوة البناء، قم باستدعاء tauri-action
`
من خلال وضع tauri-apps/tauri-action في نهاية سير العمل، سيتم تسجيل المثبتات الموقعة لكلا نظامي التشغيل تلقائياً في GitHub Release Draft في كل مرة يتم فيها دفع علامة (tag) جديدة.
عند حزم المثبت لنظام Windows، يجب أن تقرر طريقة تثبيت محرك WebView2. إذا كان اتصال الإنترنت مضموناً وكان يجب تقليل حجم ملف التحميل إلى أقصى حد، فإن طريقة downloadBootstrapper التي لا تزيد من حجم الحزمة مناسبة. وعلى العكس من ذلك، إذا كنت تستهدف شبكة مغلقة أو بيئة غير متصلة بالإنترنت، فمن الأكثر أماناً تضمين offlineInstaller حتى لو أدى ذلك إلى إضافة حوالي 127 ميجابايت إلى ملف التثبيت.
تعد الطريقة التي تتعامل بها مع البيانات عند تشغيل تطبيقات تعتمد على Tauri أمراً مهماً أيضاً. من الخطير وضع البيانات في IndexedDB أو LocalStorage الخاص بالمتصفح.
في الواقع، عند الانتقال من Tauri v1 إلى v2، حدث تغيير داخلي في نطاق مجال WebView في بيئة Windows من [https://tauri.localhost](https://tauri.localhost) إلى [http://tauri.localhost](http://tauri.localhost). وبسبب هذا، فقد العديد من المستخدمين بياناتهم الأصلية حيث تم تحويل مسار ذاكرة التخزين المؤقت للمتصفح قسراً.
لمنع كارثة مسح البيانات بعد التوزيع، يجب تخزين المعلومات الأساسية مباشرة كملف SQLite في منطقة نظام الملفات الأصلي بدلاً من تخزين الويب. إذا استخدمت واجهة برمجة تطبيقات appDataDir الخاصة بـ Tauri v2، فسيتم العثور تلقائياً على مسار "Sandbox" آمن يتوافق مع مواصفات نظام التشغيل.
C:\Users\<UserName>\AppData\Roaming\<BundleIdentifier>/Users/<UserName>/Library/Application Support/<BundleIdentifier>فيما يلي مثال على التدخل في دورة حياة التطبيق داخل كود Rust (src-tauri/src/lib.rs) الذي يعمل كخلفية لـ Tauri v2 لربط قاعدة بيانات SQLite بالمنطقة الآمنة وتشغيل ترحيل المخطط (Schema Migration):
`rust
use std::fs;
use tauri::Manager;
use tauri_plugin_sql::{Migration, MigrationKind};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let database_migrations = vec![
Migration {
version: 1,
description: "initialize_user_profiles_table",
sql: "CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);",
kind: MigrationKind::Up,
}
];
tauri::Builder::default()
.setup(|app| {
let local_app_dir = app.path().app_data_dir()
.expect("Critical: Could not resolve target operating system app data path.");
if !local_app_dir.exists() {
fs::create_dir_all(&local_app_dir)
.expect("Critical: Failed to establish persistent storage directory structure.");
}
Ok(())
})
.plugin(
tauri_plugin_sql::Builder::default()
.add_migrations("sqlite:users.db", database_migrations)
.build()
)
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
`
بهذا التكوين، سيتم حفظ قاعدة بيانات المستخدم الفعلية بأمان حتى لو تم مسح ذاكرة التخزين المؤقت الداخلية لـ Electron أو Chromium WebView بسبب التحديث التلقائي أو إعادة التثبيت.
الطريقة التي تجبر المستخدم على زيارة الصفحة الرئيسية في كل مرة لتحميل إصدار جديد تزيد من معدل المغادرة. يجب عليك بناء هيكل يقدم ملفات التحديث بهدوء من خلال دمج تخزين كائنات السحابة وCDN.
يعد الجمع بين Cloudflare R2 وAWS CloudFront فعالاً كخادم توزيع. Cloudflare R2 لا يفرض رسوم خروج (Egress Fees)، مما يجعل تكلفة حركة مرور الشبكة الناتجة عن إصدار ملفات تحديث كبيرة صفراً.
ملف البيانات الوصفية (latest.json) الذي يتحقق منه العميل لمعرفة ما إذا كان هناك إصدار جديد لا يجب تخزينه مؤقتاً في CDN أو المتصفح. يجب تحديد السياسة التالية في رأس الاستجابة:
`http
Cache-Control: no-cache, no-store, must-revalidate
`
في المقابل، نظراً لأن ملفات التثبيت الثنائية الفعلية هي حالات غير قابلة للتغيير (Immutable) تحتوي على قيم تجزئة فريدة، قم بضبطها ليتم الاحتفاظ بها في CDN لأطول فترة ممكنة لتخفيف عبء حركة المرور عن الخادم الأصلي.
`http
Cache-Control: public, max-age=31536000, immutable
`
في Tauri v2، انتقل موقع خيارات التحديث تحت كتلة plugins.updater. فيما يلي مواصفات إعداد tauri.conf.json:
`json
{
"bundle": {
"createUpdaterArtifacts": true
},
"plugins": {
"updater": {
"active": true,
"endpoints": [
"https://cdn.myapp.com/releases/latest.json"
],
"dialog": false,
"pubkey": "dW5zaWduZWQgYm91bmRmaXg...",
"windows": {
"installMode": "passive"
}
}
}
}
`
لجعل المستخدم يقوم بالتحديث في بيئة Windows دون الضغط على نوافذ تأكيد مزعجة، يجب تعيين installMode على passive أو quiet. نمط passive يعرض فقط شريط تقدم هادئ بدلاً من نافذة معالج التثبيت، ثم يكمل الاستبدال بصمت.
بمجرد اكتمال الإعداد، قم بربط @tauri-apps/plugin-updater و @tauri-apps/plugin-process في واجهة المستخدم الأمامية (Frontend) لإضافة منطق يتحقق من وجود تصحيحات جديدة عند تشغيل التطبيق ويحث على إعادة التشغيل.
`typescript
import { check } from "@tauri-apps/plugin-updater";
import { ask } from "@tauri-apps/plugin-dialog";
import { relaunch } from "@tauri-apps/plugin-process";
export async function runBackgroundUpdater(): Promise {
try {
const updatePayload = await check();
if (updatePayload && updatePayload.available) {
const userResponse = await ask(
`يتوفر إصدار جديد [v${updatePayload.version}]. هل تريد التحديث الآن وإعادة تشغيل التطبيق؟`,
{
title: "إشعار تحديث البرنامج التلقائي",
kind: "info",
okLabel: "تثبيت التحديث وإعادة التشغيل",
cancelLabel: "تطبيق لاحقاً"
}
);
if (userResponse) {
await updatePayload.downloadAndInstall();
await relaunch();
}
}
} catch (error) {
console.error("حدث خطأ أثناء عملية التحقق من التحديث التلقائي:", error);
}
}
`
بمجرد وضع هذه الدالة في مرحلة التحميل الأولية للمكون أو العرض الرئيسي في React، سيستخدم المستخدم دائماً أحدث إصدار من البرنامج دون الحاجة إلى البحث في الصفحة الرئيسية بنفسه.
appDataDir الذي يتحكم فيه التطبيق الأصلي وتشغيل ترحيل المخطط على المدى الطويل حتى لا تتداخل البيانات أثناء تحديث التطبيق.