<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های علی قادری مقدم</title><link>https://virgool.ir/feed/@0xali</link><description>سینما، تکنولوژی، AI، نرم افزار</description><language>fa</language><pubDate>2026-11-11 01:06:27</pubDate><image><url>https://files.virgool.ir/upload/users/6328/avatar/Acng0s.jpg</url><title>علی قادری مقدم</title><link>https://virgool.ir/@0xali</link></image><item><title>چطور از VPS پارس‌پک روی آبجکت‌استورج رایگان بکآپ بگیریم؟</title><link>https://virgool.ir/@0xali/بکاپ-گیری-از-سرور-mw8ea2xr48cp</link><description>امروز متوجه شدم که پارس پک یک Object Storage S3 با فضای 50GB بعنوان فضای پشتیبان گیری سرورهای ابری بصورت رایگان ارائه میده. من تصمیم گرفتم سرورم رو با rclone به این استوریج وصل کنم و یک اسکریپت هم با کمک هوشواره (هان چیه!؟) براش نوشتم که هر روز راس یک ساعت خاص در زمان کاهش ترافیک سرورم بصورت خودکار از سرور و دیتابیس یک نسخه پشتیبان ایجاد و یا Sync کنه.اول از همه در پنل کاربری پارس پک، از داشبورد مدیریت سرور گزینه فضای پشتیبان رو انتخاب کنید و یک Object Storage جدید بسازید.داشبورد مدیریت سرور ابری در پارس پکبعد از اینکه مراحل ساخت تموم میشه شبیه تصویر پایین، به شما سه تا عبارت نمایش میده که برای اتصال به استوریج بهشون نیاز داریم؛ این ها رو یک جای امن یادداشت کنید موقتا:Endpoint URLAccess KeySecret Keyهمچنین نام باکت هم مهمه که از بخش بررسی اجمالی در داشبورد میتونید ببینیدش:در بخش بررسی اجمالی در داشبوردچالش اصلی و یک نکته مهم!قبل از اینکه سراغ آموزش بریم، یک موضوع خیلی مهم وجود داره: این استوریج پارس‌پک قابلیت فایل ورژنینگ (File Versioning) رو به صورت نیتیو ساپورت نمی‌کنه! یعنی اگه فایلی رو ویرایش یا پاک کنید، نسخه قبلیش روی ابر اووررایت میشه و می‌پره.اما با اسکریپتی که با کمک داداشم هوشواره نوشتم، این مشکل رو حل کردیم؛ طوری که هر بار فایل جدیدی تغییر کنه یا پاک بشه، نسخه قبلیش توی یک پوشه تاریخ‌دار (مثل file_changes/2026-08-01/) آرشیو میشه و تا ۷ روز می‌مونه.علاوه بر این:فضای دیسک سرور اشغال نمیشه: دیتابیس مستقیماً روی فضای ابری استریم میشه (Zero-Disk).ارور 429 (Too Many Requests) نمی‌خوریم: نرخ درخواست‌ها رو کنترل کردیم تا روی بکاپ گیری های سنگین پارس‌ پک ریجکتمون نکنه.Symlinkها هم دنبال میشن: پوشه‌هایی مثل storage توی لاراول یا فایل‌های SSL که لینک هستن هم بکاپ گرفته میشن.مرحله اول: نصب و کانفیگ Rclone روی سرورابتدا ابزار Rclone رو روی سرور نصب می‌کنیم:sudo apt update &amp;&amp; sudo apt install rclone -yحالا دستور زیر رو بزنید تا مراحل کانفیگ اتصال شروع بشه:rclone configمراحل رو به این ترتیب دنبال کنید:حرف n رو تایپ کنید و Enter بزنید (New remote).یک اسم دلخواه برای استوریج انتخاب کنید (مثلاً: parspack_s3).توی لیست باز شده، گزینه مربوط به s3 (Amazon S3 Compliant Storage Provider) رو انتخاب کنید.توی بخش Provider، شماره مربوط به Any other S3 compatible provider رو بزنید.عدد 1 رو برای env_auth انتخاب کنید (پیش‌فرض false).کلید access_key_id که از پنل پارس‌پک گرفتید رو وارد کنید.کلید secret_access_key رو هم وارد کنید.توی بخش endpoint آدرس Endpoint پارس‌پک رو وارد کنید (آدرسی که توی پنل داده).بقیه گزینه‌ها رو با زدن Enter روی حالت پیش‌فرض بگذارید تا به آخر برسید و با زدن y کانفیگ رو ذخیره کنید.یه نکته مهم:پارس‌پک روی متد پیش‌فرض تست باکت S3 گیر میده و ارور میده. برای حل این مشکل، فایل کانفیگ Rclone رو باز کنید:nano ~/.config/rclone/rclone.confزیر بخش مربوط به [parspack_s3] این خط رو اضافه کنید:no_check_bucket = trueحالا با دستور زیر تست کنید ببینید اتصال برقرار هست یا نه:rclone ls parspack_s3:نام_باکت_شمادر نهایت اطلاعات شما هم باید همچین چیزی باشهمرحله دوم: ساخت اسکریپت بکاپ‌گیرییک فایل اسکریپت جدید می‌سازیم:sudo nano /usr/local/bin/backup.shکد زیر رو کاملاً داخلش کپی کنید (اطلاعات خودتون رو جایگزین کنید):#!/bin/bash&#xA;&#xA;export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin&#xA;set -o pipefail&#xA;&#xA;DATE=$(date +%Y-%m-%d_%H-%M-%S)&#xA;REMOTE=&#34;parspack_s3:نام_باکت_شما&#34;&#xA;RCLONE_CONFIG=&#34;/home/[نام_کاربری_سرور]/.config/rclone/rclone.conf&#34;&#xA;LOG_FILE=&#34;/var/log/backup.log&#34;&#xA;&#xA;# فلگ‌های بهینه‌شده Rclone جهت جلوگیری از ارور 429 پارس‌پک و پوشش Symlinkها&#xA;FLAGS=(&#xA;  &#34;--config&#34; &#34;$RCLONE_CONFIG&#34;&#xA;  &#34;--s3-no-check-bucket&#34;&#xA;  &#xA;  # دنبال کردن Symlinkها (پوشه storage لاراول و SSLها)&#xA;  &#34;-L&#34;&#xA;  &#xA;  # دریافت لیست متادیتا در یک درخواست برای کاهش شدید کلیک‌های API&#xA;  &#34;--fast-list&#34;&#xA;  &#xA;  # کنترل نرخ درخواست‌ها برای جلوگیری از Too Many Requests&#xA;  &#34;--tpslimit&#34; &#34;5&#34;&#xA;  &#34;--tpslimit-burst&#34; &#34;0&#34;&#xA;  &#34;--transfers&#34; &#34;1&#34;&#xA;  &#34;--checkers&#34; &#34;2&#34;&#xA;  &#xA;  # مدیریت هوشمند Retry در صورت ریجکت درخواست توسط S3&#xA;  &#34;--low-level-retries&#34; &#34;10&#34;&#xA;  &#34;--retries-sleep&#34; &#34;5s&#34;&#xA;  &#xA;  # تنظیمات لاگ‌گیری&#xA;  &#34;--log-file&#34; &#34;$LOG_FILE&#34;&#xA;  &#34;--log-level&#34; &#34;NOTICE&#34;&#xA;)&#xA;&#xA;# کاهش اولویت پردازش جهت جلوگیری از فشار به CPU و دیسک&#xA;RESOURCE_LIMIT=&#34;nice -n 19 ionice -c 2 -n 7&#34;&#xA;&#xA;DB_USER=&#34;یوزر_دیتابیس&#34;&#xA;DB_PASS=&#34;پسورد_دیتابیس&#34;&#xA;DB_NAME=&#34;نام_دیتابیس&#34;&#xA;APP_DIR=&#34;مسیر_مبدا_بکاپ&#34;&#xA;&#xA;# -------------------------------------------------------------&#xA;&#xA;# ۱) بکاپ و استریم مستقیم دیتابیس بدون درگیر کردن دیسک سرور&#xA;$RESOURCE_LIMIT mysqldump -u&#34;$DB_USER&#34; -p&#34;$DB_PASS&#34; --single-transaction --quick &#34;$DB_NAME&#34; \&#xA;  | gzip \&#xA;  | $RESOURCE_LIMIT rclone rcat &#34;$REMOTE/database/db_$DATE.sql.gz&#34; &#34;${FLAGS[@]}&#34;&#xA;&#xA;# ۲) سینک هوشمند فایل‌های پروژه (پیاده‌سازی ورژنینگ دستی با backup-dir)&#xA;$RESOURCE_LIMIT rclone sync &#34;$APP_DIR&#34; &#34;$REMOTE/current_files/&#34; \&#xA;  --backup-dir &#34;$REMOTE/file_changes/$DATE/&#34; \&#xA;  --exclude &#39;vendor/**&#39; \&#xA;  --exclude &#39;node_modules/**&#39; \&#xA;  --exclude &#39;.git/**&#39; \&#xA;  --exclude &#39;*.log&#39; \&#xA;  --exclude &#39;*.tmp&#39; \&#xA;  &#34;${FLAGS[@]}&#34;&#xA;&#xA;# ۳) پاک‌سازی خودکار بکاپ‌های قدیمی‌تر از ۷ روز از روی ابر&#xA;$RESOURCE_LIMIT rclone delete &#34;$REMOTE/database/&#34; --min-age 7d &#34;${FLAGS[@]}&#34;&#xA;$RESOURCE_LIMIT rclone delete &#34;$REMOTE/file_changes/&#34; --min-age 7d &#34;${FLAGS[@]}&#34;&#xA;$RESOURCE_LIMIT rclone rmdirs &#34;$REMOTE/file_changes/&#34; &#34;${FLAGS[@]}&#34; 2&gt;/dev/null&#xA;دسترسی اجرای امن به اسکریپت می‌دیم:sudo chmod 700 /usr/local/bin/backup.shاطلاعات زیر رو در اسکریپت جایگزین کنید:نام_باکت_شمانام_کاربری_سرور (مثلا ali)DB_USER=&#34;یوزر_دیتابیس&#34;DB_PASS=&#34;پسورد_دیتابیس&#34;DB_NAME=&#34;نام_دیتابیس&#34;APP_DIR=&#34;مسیر_مبدا_بکاپ&#34; (مسیری که قصد دارید از فایل هاش بکاپ بگیرید)مرحله سوم: تنظیم اجرای اتوماتیک (Cron Job)می‌خوایم اسکریپت هر شب رأس ساعت مشخصی خودش اجرا بشه. crontab کاربر root رو باز می‌کنیم:sudo crontab -e&#xA;این خط رو به انتهاش اضافه کنید:Code snippet30 23 * * * /usr/local/bin/backup.sh &gt;/dev/null 2&gt;&amp;1&#xA;(نکته: چون تایم‌زون سرور UTC هست، ۲۳:۳۰ UTC دقیقاً معادل ۳:۰۰ بامداد تهران میشه که خلوت‌ترین زمان سروره).جمع‌بندیبه همین راحتی تونستیم بدون پرداخت هزینه اضافی و بدون اینکه نگران پر شدن فضای VPS یا پریدن ورژن‌های قدیمی فایل‌ها باشیم، یک سیستم بکاپ‌گیری اتوماتیک بسازیم!در نهایت میتونید اسکریپت رو بر حسب نیاز خودتون با کمک هوشواره تغییر بدید (مثلا بخش مربوط به بکاپ گیری از دیتابیس رو حذف کنید یا مدت زمان نگه داری بکاپ های قدیمی رو از 7 روز به یک ماه تغییر بدید)</description><category>علی قادری مقدم</category><author>علی قادری مقدم</author><pubDate>Mon, 03 Aug 2026 12:34:47 +0330</pubDate></item><item><title>توسعه اندروید در شرایط قطعی اینترنت بین‌الملل (Gradle, Maven و میرورها)</title><link>https://virgool.ir/@0xali/توسعه-اندروید-در-شرایط-قطعی-اینترنت-بین-الملل-gradle-maven-و-میرورها-ick8qcg53ymq</link><description>گریدل و داستان هاش!وقتی mirror های معمول جواب نمی‌دن یک راه پایدارتر برای build گرفتن از پروژه‌های Flutter و Androidاگر این چند وقت درگیر build گرفتن از پروژه‌های Flutter یا Android بودی احتمالن این صحنه برات آشناست:همه‌چیز ظاهرن درست تنظیم شده، mirror هم گذاشتی، حتی آدرس wrapper و repositoryها رو هم عوض کردی، ولی Gradle باز وسط کار میره سراغ یه URL دیگه و همه‌چیز همون‌جا متوقف میشه. گاهی با timeout، گاهی با پیدا نشدن plugin، گاهی هم با خطاهایی که در نگاه اول اصلاً معلوم نیست از کجا اومدن.چیزی که خیلی‌ها اول متوجهش نمی‌شن اینه که استفاده از mirror فقط به این خلاصه نمیشه که چند خط توی settings.gradle.kts یا build.gradle عوض کنی. روی کاغذ، این روش‌ها درستن. حتی بعضی وقت‌ها هم جواب میدن. ولی در عمل مخصوصا تو پروژه‌های جدید Flutter، همه‌ی درخواست‌های Gradle از یک مسیر رد نمی‌شن. یه بخش مربوط به dependencyهاست، یه بخش مربوط به pluginها، یه بخش مربوط به Gradle Wrapper، و تازه خود Flutter هم بعضی وقت‌ها buildهای داخلی خودش رو وارد داستان می‌کنه. برای همین ممکنه تو همه‌چیز رو داخل پروژه درست تنظیم کرده باشی، اما باز هم بخشی از build از زیر دستت رد بشه و بره سراغ منبع اصلی یا یه مخزن دیگه.اینجاست که تفاوت بین یک راه‌حل موقت و یک راه‌حل پایدارتر مشخص میشه.برای استفاده از mirror، راه‌های مختلفی وجود داره. بعضی‌ها فقط gradle-wrapper.properties رو تغییر میدن. بعضی‌ها میرن سراغ pluginManagement و dependencyResolutionManagement داخل settings.gradle.kts. بعضی‌ها هم repositoryهای build.gradle رو می‌بندن و فکر می‌کنن کار تموم شده. این روش‌ها بد نیستن، ولی یه مشکل مشترک دارن: بیشترشون محدود به همون پروژه‌ان. یعنی فقط همون بخشی رو کنترل می‌کنن که جلوی چشم تو قرار داره. اگر Gradle یا Flutter در لایه‌ای پایین‌تر یا در یک build داخلی، repository دیگه‌ای اضافه کنه، اون تنظیم‌ها همیشه کافی نیستن.به همین خاطر، توی تجربه‌ی عملی، یکی از پایدارترین روش‌ها استفاده از این فایل init.d\mirror.init.gradle.kts هست.دلیلش هم خیلی پیچیده نیست. این فایل دیگه فقط تنظیم یک پروژه نیست بلکه خود Gradle رو در سطح سیستم تنظیم میکنی. یعنی هر repository که در جریان build اضافه بشه، از یک لایه بالاتر بررسی و در صورت نیاز rewrite میشه. به زبان ساده‌تر، به‌جای اینکه امیدوار باشی همه‌ی پروژه‌ها و همه‌ی buildهای داخلی دقیقاً همون repoهایی رو بخونن که تو داخل فایل‌های پروژه گذاشتی، خود رفتار Gradle رو هدایت می‌کنی. همین باعث میشه این روش در سناریوهایی که plugin resolution، included buildها یا dependencyهای پنهان وسط کار وارد ماجرا می‌شن، معمولاً پایدارتر از بقیه روش‌ها عمل کنه.توی تجربه‌ی من، بین mirror های ایرانی که برای توسعه‌ی اندروید امتحان کردم، https://maven.myket.ir از کامل‌ترین گزینه‌ها بود و روی بعضی پروژه‌ها که با mirrorهای دیگه build نمی‌شدن، نتیجه‌ی بهتری داد. دقیق‌تر بگم، بعضی mirrorها برای dependencyهای معمولی خوب عمل می‌کردن، ولی وقتی build به pluginها، included buildهای Flutter یا artifactهای خاص‌تر می‌رسید، دوباره خطا برمیگشت. برای همین اگر هدفت اینه که یک راه‌حل عملی و نسبتا پایدار داشته باشی، مایکت فعلا یکی از گزینه‌های خوبه.برای پیاده سازی این روش لازمه این فایل رو در مسیر زیر بسازی و این کدهارو داخلش کپی و سیو کنی: مسیر کامل توی ویندوز:C:\Users\&lt;username&gt;\.gradle\init.d\mirror.init.gradle.ktsنام فایل mirror.init.gradle.kts &lt;username&gt;= نام کاربری شما(اگر پوشه init.d وجود نداره بسازش)محتوای فایل:import org.gradle.api.artifacts.dsl.RepositoryHandler&#xA;import org.gradle.api.artifacts.repositories.MavenArtifactRepository&#xA;&#xA;val MIRROR = &#34;https://maven.myket.ir&#34;&#xA;&#xA;val REDIRECT_HOSTS = setOf(&#xA;    &#34;repo.maven.apache.org&#34;,&#xA;    &#34;repo1.maven.org&#34;,&#xA;    &#34;dl.google.com&#34;,&#xA;    &#34;maven.google.com&#34;,&#xA;    &#34;plugins.gradle.org&#34;,&#xA;    &#34;jitpack.io&#34;,&#xA;    &#34;jcenter.bintray.com&#34;,&#xA;    &#34;maven.fabric.io&#34;,&#xA;    &#34;developer.huawei.com&#34;,&#xA;    &#34;storage.googleapis.com&#34;,&#xA;)&#xA;&#xA;fun shouldRedirect(url: String): Boolean =&#xA;    REDIRECT_HOSTS.any { host -&gt; url.contains(host, ignoreCase = true) }&#xA;&#xA;fun interceptRepos(repos: RepositoryHandler) {&#xA;    repos.whenObjectAdded {&#xA;        val repo = this as? MavenArtifactRepository ?: return@whenObjectAdded&#xA;        val original = repo.url.toString()&#xA;&#xA;        if (shouldRedirect(original)) {&#xA;            println(&#34;[Myket Mirror] Redirecting: $original -&gt; $MIRROR&#34;)&#xA;            repo.setUrl(MIRROR)&#xA;        }&#xA;    }&#xA;}&#xA;&#xA;gradle.beforeSettings {&#xA;    pluginManagement {&#xA;        interceptRepos(repositories)&#xA;        repositories {&#xA;            maven(url = MIRROR)&#xA;        }&#xA;    }&#xA;&#xA;    @Suppress(&#34;UnstableApiUsage&#34;)&#xA;    dependencyResolutionManagement {&#xA;        interceptRepos(repositories)&#xA;        repositories {&#xA;            maven(url = MIRROR) {&#xA;                name = &#34;myket-mirror&#34;&#xA;            }&#xA;        }&#xA;    }&#xA;}&#xA;&#xA;gradle.allprojects {&#xA;    buildscript {&#xA;        interceptRepos(repositories)&#xA;        repositories {&#xA;            clear()&#xA;            maven(url = MIRROR)&#xA;        }&#xA;    }&#xA;&#xA;    interceptRepos(repositories)&#xA;&#xA;    repositories.whenObjectAdded {&#xA;        val repo = this as? MavenArtifactRepository ?: return@whenObjectAdded&#xA;        val original = repo.url.toString()&#xA;&#xA;        if (shouldRedirect(original)) {&#xA;            println(&#34;[Myket Mirror] Redirecting project repo: $original -&gt; $MIRROR&#34;)&#xA;            repo.setUrl(MIRROR)&#xA;        }&#xA;    }&#xA;}فقط این نکته رو یادت باشه که این فایل، جای تنظیم wrapper رو نمی‌گیره. یعنی اگر خواستی دانلود خود Gradle هم از مایکت انجام بشه، باید gradle-wrapper.properties هم جداگانه تنظیم کنی:مسیر فایل: project_folder\android\gradle\wrapper\gradle-wrapper.propertiesدر این فایل به دنبال distributionUrl بگرد و با عبارت زیر جایگزینش کن:distributionUrl=https\://maven.myket.ir/gradle/distributions/gradle-8.12-all.zipو تمام!البته اینجا باید روی یک نکته تأکید کرد: پایدارتر بودن به معنی بی‌نقص بودن نیست. این روش فقط مسیر resolve شدن repositoryها رو بهتر کنترل می‌کنه. اگر mirror تو واقعاً artifact یا plugin لازم رو نداشته باشه، build باز هم fail میشه. init script قرار نیست چیزی رو از هیچ بسازه؛ فقط کمک می‌کنه درخواست‌ها از مسیری عبور کنن که تو می‌خوای، نه از مسیری که Gradle به‌صورت پیش‌فرض ترجیح میده. برای همین وقتی بعد از این تنظیم باز هم خطا می‌بینی، باید اول از همه این سؤال رو بپرسی: آیا هنوز درخواست‌ها دارن به مقصد اشتباه میرن، یا این بار درست به mirror میرن ولی خود mirror پاسخ کامل نداره؟ همین فرق ظاهراً کوچک، توی عیب‌یابی خیلی تعیین‌کننده‌ست.و در نهایت مهم‌ترین نکته‌ی این مقاله شاید همین باشه:بهترین حالت همچنان اینه که اصلن از mirror استفاده نکنی و مستقیم از منابع اصلی دانلود انجام بشه. این همیشه تمیزترین، قابل‌پیش‌بینی‌ترین و کم‌دردسرترین حالت ممکنه. mirror هرچقدر هم خوب و سریع باشه، باز یک واسطه است. ممکنه cache شدن بعضی artifactها زمان ببره ممکنه بعضی dependencyها کامل پشتیبانی نشن یا ممکنه رفتارش در بعضی سناریوها دقیقن شبیه منبع اصلی نباشه. برای همین این راهکارها رو بهتره به چشم یک راه‌حل موقت و عملی برای شرایط خاص ببینی، نه نسخه‌ی نهایی و ایده‌آل برای همه‌ی پروژه‌ها.</description><category>علی قادری مقدم</category><author>علی قادری مقدم</author><pubDate>Sun, 19 Apr 2026 18:05:08 +0330</pubDate></item></channel></rss>