ذخیره سازی ابری
پلتفرم Songbird از یک معماری ذخیره سازی ماژولار (Pluggable Storage) پشتیبانی میکند که به شما امکان می دهد بین ذخیره سازی دیسک محلی و ذخیره سازی ابری سازگار با S3 انتخاب کنید. در ترکیب با Media Worker یکپارچه Songbird، امکان ترنسکد ویدیویی غیرهمگام با کارایی بالا، استخراج thumbnail و بهینه سازی مدیا در استقرارهای تک سروری یا محیط های ابری توزیع شده فراهم شده است.
درایورهای ذخیره سازی (Storage Drivers)
Songbird از متغیر محیطی STORAGE_DRIVER برای تعیین محل ذخیره سازی فایل های آپلودشده چت، پیوست های چندرسانه ای و آواتارها استفاده میکند.
| درایور | STORAGE_DRIVER | توضیح |
|---|---|---|
| دیسک محلی (پیشفرض) | local | آپلودها مستقیماً روی فایل سیستم محلی سرور در مسیر پوشه DATA_DIR (پوشه های data/uploads/ و data/avatars/) ذخیره میشوند. ایده آل برای استقرارهای ساده روی یک سرور VPS یا Docker. |
| ذخیره سازی ابری ریموت | remote | آپلودها در یک باکت سازگار با S3 (مانند AWS S3، Cloudflare R2، MinIO، ArvanCloud، Wasabi) ذخیره میشوند. فرآیند آپلود با استفاده از Presigned URL مستقیماً بین کلاینت و باکت انجام شده و از سرور اصلی عبور نمیکند. |
# نمونه تنظیم در فایل .env
STORAGE_DRIVER=remoteمعماری آپلود مستقیم با Presigned URL
هنگامی که STORAGE_DRIVER=remote فعال است، Songbird از جریان کاری آپلود مستقیم کلاینت به باکت با Presigned URL استفاده میکند. محتوای فایل مستقیماً از مرورگر کاربر به Cloudflare R2 یا AWS S3 استریم میشود و سرور برنامه Node.js Songbird به طور کامل بایپاس میشود.
+--------+ 1. POST /api/uploads/presign +-----------------+
| | -------------------------------------------------> | |
| | <------------------------------------------------- | |
| | 2. Return presigned PUT upload URL | Songbird App |
| | | Server |
| Client | 4. POST /api/uploads/complete | (Node.js / API) |
| Browser| -------------------------------------------------> | |
| | <------------------------------------------------- | |
| | 5. File linked in database +-----------------+
| |
| | 3. HTTP PUT (Direct file upload) +-----------------+
| | -------------------------------------------------> | Cloudflare R2 / |
| | <------------------------------------------------- | S3 Storage |
+--------+ 200 OK Response +-----------------+جریان آپلود مستقیم
- درخواست Presign:
- مرورگر کلاینت متادیتا فایل را به بک اند Songbird ارسال میکند.
- سرور احراز هویت را اعتبارسنجی میکند، محدودیت حجم فایل را بررسی میکند، یک کلید ذخیره سازی یکتا میسازد و یک Presigned URL از نوع
PUTتولید میکند. - سرور یک رکورد آپلود معلق در جدول
pending_presigned_uploadsدیتابیس ثبت میکند.
- آپلود مستقیم مرورگر:
- مرورگر کلاینت باره (payload) فایل را مستقیماً با استفاده از Presigned URL به اندپوینت ذخیره سازی Cloudflare R2 / S3 آپلود میکند.
- تکمیل آپلود:
- پس از موفقیت آمیز بودن آپلود، کلاینت به Songbird اطلاع میدهد یا فایل را به درخواست پیام متصل میکند.
- سرویس Songbird کلید
storageKeyرا به دیتابیس فایل های پیام (chat_message_files) متصل کرده و تابعremovePendingPresignedUploadsرا برای پاک سازی رکورد معلق فراخوانی میکند.
مزایای آپلود مستقیم با Presigned URL
- عدم مصرف منابع سرور (Zero Overhead): انتقال فایل های سنگین هیچ مقدار از RAM یا CPU سرور Node.js را اشغال نمیکند.
- بایپاس محدودیت های سرور و پروکسی: حجم آپلود محدود به محدودیت های body-parser در Nginx یا Express (
client_max_body_size) نمیشود. - پهنای باند و سرعت بالاتر: کلاینت ها با حداکثر پهنای باند شبکه به شبکه لبه جهانی Cloudflare یا زیرساخت S3 متصل میشوند.
جریان دانلود Presigned و CDN Fallback
برای دانلود فایل ها و استریم رسانه:
GET /api/uploads/presign-download: یک Presigned URL موقت از نوعGETبرای فایل های خصوصی تولید میکند.- هنگام تنظیم
STORAGE_PUBLIC_URL: اگر یک CDN عمومی یا دامین اختصاصی تنظیم شده باشد (مانندSTORAGE_PUBLIC_URL=https://media.example.com)، Songbird آدرس های مستقیم CDN عمومی (https://media.example.com/uploads/...) را بدون نیاز به امضای دیجیتال تولید میکند. - عدم تنظیم
STORAGE_PUBLIC_URL: آدرس های امضاشده S3 با مدت اعتبارSTORAGE_EXPIRES_INبه ثانیه (پیش فرض: 3600 ثانیه / 1 ساعت) تولید میشوند.
پیکربندی ذخیره سازی ابری ریموت
هنگامی که STORAGE_DRIVER=remote فعال است، متغیرهای محیطی زیر را برای اتصال Songbird به باکت ذخیره سازی ابری خود تنظیم کنید:
| متغیر | نوع | پیش فرض | توضیح |
|---|---|---|---|
STORAGE_DRIVER | string | local | برای فعالسازی ذخیره سازی ابری، روی remote تنظیم کنید. |
STORAGE_ENDPOINT | string | "" | نشانی URL پایه API سرویس ذخیره سازی ابری (مانند https://<ACCOUNT_ID>.r2.cloudflarestorage.com برای Cloudflare R2). |
STORAGE_BUCKET | string | "" | نام باکت (Bucket) ذخیره سازی شما (مانند songbird-media). |
STORAGE_REGION | string | auto | منطقه جغرافیایی باکت (پیش فرض auto برای Cloudflare R2، MinIO، ArvanCloud؛ کاربران AWS S3 منطقه خود مانند us-east-1 را تنظیم کنند). |
STORAGE_ACCESS_KEY_ID | string | "" | کلید دسترسی (Access Key ID) برای احراز هویت باکت. |
STORAGE_SECRET_ACCESS_KEY | string | "" | کلید سرّی (Secret Access Key) برای احراز هویت باکت. |
STORAGE_PUBLIC_URL | string | "" | پیشوند URL اختیاری CDN یا دامین اختصاصی (مانند https://media.example.com). در صورت تنظیم، لینک های دانلود به جای Presigned URL از این پیشوند استفاده میکنند. |
STORAGE_EXPIRES_IN | integer | 3600 | مدت زمان اعتبار لینک های Presigned برای آپلود و دانلود به ثانیه. |
STORAGE_FORCE_PATH_STYLE | boolean | true | فعالسازی ساختار URL مبتنی بر مسیر (endpoint/bucket/key). برای Cloudflare R2، MinIO، ArvanCloud و Wasabi مقدار true و برای AWS S3 مقدار false قرار دهید. |
الزامات پیکربندی CORS در Cloudflare R2 و S3
از آنجا که مرورگر کلاینت فایل ها را مستقیماً با استفاده از درخواست های HTTP PUT بر روی Presigned URL به اندپوینت Cloudflare R2 یا S3 آپلود میکند، تنظیم CORS (Cross-Origin Resource Sharing) روی باکت الزامی است. بدون تنظیم صحیح CORS، مرورگر درخواست های آپلود را به دلیل خطای Cross-Origin مسدود خواهد کرد.
پیکربندی الزامی CORS JSON
تنظیمات JSON زیر را در بخش CORS باکت Cloudflare R2 یا S3 خود اعمال کنید:
[
{
"AllowedOrigins": [
"https://chat.example.com"
],
"AllowedMethods": [
"GET",
"PUT",
"HEAD",
"POST"
],
"AllowedHeaders": [
"*"
],
"ExposeHeaders": [
"ETag"
],
"MaxAgeSeconds": 3600
}
]INFO
آدرس https://chat.example.com را با دامین واقعی خود جایگزین کنید (یا برای تست اولیه از * استفاده کنید). متد PUT، هدرهای مجاز * و هدر اکسپوز شده ETag برای کارکرد صحیح آپلود مستقیم مرورگر ضروری هستند.
تنظیم CORS در باکت Railway
برنامه خودش سیاست CORS باکت را تنظیم میکند. هم هنگام اجرا بررسی میکند و مهم تر اینکه با هر درخواست Presigned-URL نیز با استفاده از آدرس همان درخواست بررسی مجدد میکند — پس حتی دامینی که بعد از اولین دیپلوی متصل شود، از همان آپلود بعدی بدون نیاز به دیپلوی مجدد کار میکند. قوانین با سیاست موجود ادغام میشود و هنگام تغییر عبارت Bucket CORS policy applied در لاگ نوشته میشود. پیکربندی خودکار با STORAGE_AUTO_CORS=true فعال میشود (در .railway/railway.ts از پیش تنظیم شده است).
اگر در لاگ سرور هشدار پیکربندی خودکار دیدید (مثلاً دسترسی s3:PutBucketCORS در کلیدهای باکت نیست)، یک بار دستی از هر سیستمی که به اینترنت دسترسی دارد با اطلاعات تب Credentials باکت این اسکریپت را اجرا کنید (BUCKET همان نام هشدار S3 است، نه نام نمایشی):
npm --prefix server run storage:cors -- --origin https://your-app.up.railway.appاطلاعات اتصال از روی فلگ ها یا متغیرهای محیطی خوانده میشود (--bucket / STORAGE_BUCKET، --endpoint / STORAGE_ENDPOINT، --region / STORAGE_REGION، --access-key-id، --secret-access-key)، پس میتوانید مقادیر Railway را مستقیم بدهید بدون اینکه .env را تغییر دهید:
STORAGE_BUCKET=<BUCKET> STORAGE_ENDPOINT=<STORAGE_ENDPOINT> STORAGE_REGION=<REGION> \
STORAGE_ACCESS_KEY_ID=<ACCESS_KEY_ID> STORAGE_SECRET_ACCESS_KEY=<SECRET_ACCESS_KEY> \
npm --prefix server run storage:cors -- --origin https://your-app.up.railway.appبا فلگ --show میتوانید سیاست فعلی را بدون تغییر نمایش دهید. مقدار --origin را با دامین عمومی برنامه خود جایگزین کنید (دقیقاً scheme به همراه host، بدون اسلش انتهایی؛ برای چند دامین قابل تکرار است). نیازی به دیپلوی مجدد نیست — سیاست بلافاصله اعمال میشود.
روش جایگزین با AWS CLI
AWS_ACCESS_KEY_ID=<ACCESS_KEY_ID> \
AWS_SECRET_ACCESS_KEY=<SECRET_ACCESS_KEY> \
aws s3api put-bucket-cors \
--bucket <BUCKET> \
--endpoint-url <STORAGE_ENDPOINT> \
--region <REGION> \
--cors-configuration '{
"CORSRules": [
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "HEAD", "POST"],
"AllowedOrigins": ["https://your-app.up.railway.app"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
}'راهنمای راه اندازی Cloudflare R2
سرویس Cloudflare R2 یک ذخیره سازی ابری سازگار با S3 بدون هزینه پهنای باند خروجی (Egress) است که آن را به گزینه ای عالی برای میزبانی فایل های پیوست و آواتارهای Songbird تبدیل میکند.
گام 1: ساخت باکت R2
- وارد داشبورد Cloudflare شوید.
- از منوی کناری به بخش R2 Object Storage بروید.
- روی Create bucket کلیک کنید.
- نام باکت (مانند
songbird-media) را وارد کرده و روی Create Bucket کلیک کنید.
گام 2: ساخت کلیدها و توکن های API
- در صفحه R2 Overview روی Manage R2 API Tokens کلیک کنید.
- روی Create API Token کلیک کنید.
- در بخش Permissions گزینه Object Read & Write را انتخاب کنید.
- در بخش Apply to specific buckets باکت خود (
songbird-media) را انتخاب کنید. - روی Create API Token کلیک کنید.
- کلیدهای تولیدشده را در مکان امن ذخیره کنید:
- Access Key ID →
STORAGE_ACCESS_KEY_ID - Secret Access Key →
STORAGE_SECRET_ACCESS_KEY
- Access Key ID →
- شناسه اکانت (Account ID) خود را از URL داشبورد کپی کنید (مانند
https://dash.cloudflare.com/<ACCOUNT_ID>/r2).
گام 3: پیکربندی قانون CORS باکت
- باکت خود را در Cloudflare R2 باز کرده و تب Settings را انتخاب کنید.
- به بخش CORS Policy بروید و روی Edit CORS Policy کلیک کنید.
- کد CORS JSON بالا را پیست کرده و ذخیره کنید.
گام 4: تنظیم متغیرهای محیطی .env در Songbird
تنظیمات زیر را به فایل .env خود اضافه کنید:
STORAGE_DRIVER=remote
STORAGE_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com
STORAGE_BUCKET=songbird-media
STORAGE_REGION=auto
STORAGE_ACCESS_KEY_ID=<R2_ACCESS_KEY_ID>
STORAGE_SECRET_ACCESS_KEY=<R2_SECRET_ACCESS_KEY>
STORAGE_FORCE_PATH_STYLE=true
STORAGE_EXPIRES_IN=3600گام 5: (اختیاری) دامین اختصاصی یا URL عمومی R2
به صورت پیش فرض Songbird لینک های امن Presigned برای دانلود فایل ها تولید میکند. اگر Public Access یا Custom Domain (مانند media.example.com) را در تنظیمات R2 فعال کرده اید، متغیر STORAGE_PUBLIC_URL را مقداردهی کنید:
STORAGE_PUBLIC_URL=https://media.example.comنمونه های پیکربندی سرویس دهندگان
AWS S3
STORAGE_DRIVER=remote
STORAGE_ENDPOINT=https://s3.us-east-1.amazonaws.com
STORAGE_BUCKET=my-songbird-bucket
STORAGE_REGION=us-east-1
STORAGE_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
STORAGE_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
STORAGE_FORCE_PATH_STYLE=falseخلاصه تنظیمات Cloudflare R2
STORAGE_DRIVER=remote
STORAGE_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com
STORAGE_BUCKET=my-songbird-bucket
STORAGE_REGION=auto
STORAGE_ACCESS_KEY_ID=<R2_ACCESS_KEY_ID>
STORAGE_SECRET_ACCESS_KEY=<R2_SECRET_ACCESS_KEY>
STORAGE_PUBLIC_URL=https://media.example.com
STORAGE_FORCE_PATH_STYLE=trueMinIO (سرویس شخصی)
STORAGE_DRIVER=remote
STORAGE_ENDPOINT=http://minio.internal:9000
STORAGE_BUCKET=songbird-media
STORAGE_REGION=us-east-1
STORAGE_ACCESS_KEY_ID=minioadmin
STORAGE_SECRET_ACCESS_KEY=minioadmin
STORAGE_FORCE_PATH_STYLE=trueذخیره سازی ابری آروان کلاد (ArvanCloud)
STORAGE_DRIVER=remote
STORAGE_ENDPOINT=https://s3.ir-thr-at1.arvanstorage.ir
STORAGE_BUCKET=my-songbird-bucket
STORAGE_REGION=ir-thr-at1
STORAGE_ACCESS_KEY_ID=<ARVAN_ACCESS_KEY>
STORAGE_SECRET_ACCESS_KEY=<ARVAN_SECRET_KEY>
STORAGE_FORCE_PATH_STYLE=trueردیابی آپلودهای معلق و پاکسازی فایل های رهاشده
برای جلوگیری از تجمع فایل های رهاشده در باکت های ابری (در صورت لغو آپلود یا بستن مرورگر توسط کاربر)، Songbird از مکانیزم ردیابی و پاکسازی دورهای خودکار استفاده میکند:
ردیابی آپلودهای معلق (Pending Upload Tracking)
- هنگام درخواست Presigned URL توسط کلاینت (
POST /api/uploads/presign)، یک رکورد شاملstorage_key،user_idو زمان ساخت در جدولpending_presigned_uploadsثبت میشود. - زمانی که آپلود با موفقیت انجام شد و فایل به پیام چت متصل گردید، Songbird با فراخوانی
removePendingPresignedUploadsاین رکورد معلق را حذف میکند.
پاکسازی دورهای پس زمینه (Periodic Background Pruning)
- سرویس نگهداشت پس زمینه به صورت دورهای تابع
pruneOrphanRemoteObjectsرا اجرا میکند. - رکوردهای قدیمی تر از 1 ساعت در جدول
pending_presigned_uploadsاستعلام میشوند. - برای هر رکورد منقضی شده، بررسی میشود که آیا
storage_keyدر جدولchat_message_filesوجود دارد یا خیر. - در صورتی که فایل به هیچ پیامی متصل نشده باشد، فایل رهاشده مستقیماً از باکت ابری (توسط دستور
DeleteObjectCommand) حذف شده و رکورد معلق پاکسازی میشود.
معماری Media Worker یکپارچه
سرویس Songbird دارای یک Media Worker مستقل و بدون وضعیت (Stateless HTTP Push Worker) در پوشه worker/ است که وظیفه انجام ترنسکد ویدیوها، استخراج تصاویر thumbnail و بررسی متادیتای فایل های مدیا را خارج از سرور اصلی چت بر عهده دارد.
راهنمای اختصاصی ورکر مدیا
برای بررسی جامع استقرار مستقل، راه اندازی ابری (Render، Railway، Fly.io)، تنظیم همزمانی و مستندات API، به صفحه اختصاصی ورکر مدیا مراجعه کنید.
حالت های رمزنگاری ریموت (STORAGE_ENCRYPTION_MODE)
Songbird دو استراتژی مختلف برای رمزنگاری هنگام استفاده از ذخیره سازی ابری ارائه میدهد:
| حالت | STORAGE_ENCRYPTION_MODE | توضیح |
|---|---|---|
| رمزنگاری سمت ارائه دهنده (پیشفرض) | remote | از رمزنگاری پیشفرض باکت S3 (مانند SSE-S3 / AES-256) استفاده میکند. درخواست های دانلود با ریدایرکت مستقیم 302 به باکت/CDN متصل میشوند که بالاترین سرعت را فراهم میکند. |
| رمزنگاری Envelope سمت اپلیکیشن | local | از رمزنگاری متقارن AES-256-GCM با کلید اختصاصی سرور (STORAGE_ENCRYPTION_KEY) استفاده میکند. فایل ها پیش از ارسال به کلاینت رمزگشایی میشوند. |
# رمزنگاری سمت ارائه دهنده باکت (پیشنهادی برای Cloudflare R2 / AWS S3 همراه با CDN)
STORAGE_ENCRYPTION_MODE=remote
# رمزنگاری لایه اپلیکیشن
STORAGE_ENCRYPTION_MODE=localاستفاده از Redis و صف های کاری (BullMQ)
Songbird از BullMQ برای مدیریت کارهای پس زمینه استفاده میکند.
حالت های کاری BullMQ
فعال بودن Redis (
REDIS_HOSTیاREDIS_URLتنظیم شده):- به Redis متصل شده و صف های BullMQ و ورکرهای ماندگار را فعال میکند.
- کارهای پردازش رسانه به صورت ایمن بین چند سرور توزیع میشوند.
حالت Fallback بدون Redis:
- در صورت عدم تنظیم Redis، به صف درون برنامه ای (In-Memory) سوییچ میکند و بدون نیاز به نصب هیچ نرم افزار اضافی کار میکند.
# تنظیمات اختیاری Redis برای توزیع بار در چند سرور
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
# یا
REDIS_URL=redis://:password@redis-server:6379/0