تثبيت إصدار Node
يعمل إصدار Node خدمةً دائمة: سيرورة واحدة، وقاعدة SQLite على القرص، ولا شيء آخر يُثبَّت — لا قاعدة بيانات خارجية، ولا ذاكرة وسيطة، ولا طابور. يمضي هذا الدليل من مجلد فارغ إلى نسخة في الإنتاج، بما في ذلك ما تتركه أكثر الأدلة جانبًا: خدمة النظام، والوسيط العكسي، وTLS، والنسخ الاحتياطية، والترقية.
ما يلزم#
| العنصر | الإصدار | لماذا |
|---|---|---|
| Node.js | 22 أو أحدث | يعتمد المنتج على مُشغّل الاختبارات المدمج وعلى واجهات حديثة. |
| مترجم C | build-essential | يُترجَم better-sqlite3 عند التثبيت، ما لم يوجد ملف ثنائي جاهز لمنصّتك. |
| قرص محلي | — | لـ SQLite وللملفات المرفوعة. ولا مشاركة شبكية أبدًا: انظر أدناه. |
جهاز بنواة واحدة و512 ميغابايت من الذاكرة يكفي لبضع عشرات من الأشخاص. وما يهمّ ليس القوة بل القرص: يجب أن يكون محليًّا ومحفوظًا احتياطيًّا.
١. الجلب والتثبيت#
git clone <مستودعك> /var/www/toutadmin
cd /var/www/toutadmin
npm ci --omit=dev
npm ci لا npm install: فهو يثبّت تمامًا ما يصفه ملف القفل، ولا يعيد
كتابته أبدًا. وعلى خادم، نسخة تنزلق هي عطل لا يفهمه أحد.
better-sqlite3 هو الاعتمادية الوحيدة التي تترجم شفرة C. و
sudo apt install -y build-essential python3 يحلّ جُلّ الحالات.
٢. الإعداد#
cp .env.example .env
كل شيء يُضبط بمتغيرات البيئة — تُقرأ من .env أو يضعها مدير الخدمات عندك. ولا واحد
منها إلزامي: فبلا .env تنطلق النسخة على المنفذ 3000 وترسلك إلى المعالج.
| المتغير | الافتراضي | ماذا يفعل |
|---|---|---|
PORT | 3000 | منفذ الإصغاء. |
NODE_ENV | — | production في الإنتاج: كعكات صارمة، وبلا آثار مفصّلة. |
SESSION_SECRET | يُولَّد | يختم الجلسات. وإن تُرك فارغًا وُلِّد في data/session.key. |
INSTALL_TOKEN | — | يطلبه المعالج قبل التثبيت. ويُنصح به على خادم مكشوف. |
TRUST_PROXY | — | 1 خلف وسيط موثوق، وهناك وحده. |
DB_PATH | data/app.sqlite | قاعدة البيانات. |
UPLOAD_DIR وCV_DIR وVAULT_DIR وSIGN_DIR وDOCS_DIR وBACKUP_DIR | داخل data/ | مجلدات الملفات: الصور، والسير الذاتية، والخزنة، وملف التوقيع، والمستندات الواردة، والأرشيفات. |
LOGIN_RATE_LIMIT | 10 | محاولات الدخول لكل ربع ساعة ولكل عنوان. |
GLOBAL_RATE_LIMIT | 300 | الطلبات في الدقيقة ولكل عنوان. |
API_RATE_LIMIT | — | نداءات واجهة البرمجة في الدقيقة ولكل رمز. |
SESSION_IDLE_MINUTES | 60 | مدة الخمول التي تسقط بعدها الجلسة. |
SESSION_MAX_HOURS | 12 | مدة مطلقة لا يمدّدها أي نشاط. |
ADMIN_EMAIL وADMIN_PASSWORD | — | تثبيت بلا واجهة: يُنشئ المسؤول عند الإقلاع. |
تغيير SESSION_SECRET يُخرج الجميع دفعةً واحدة. ضعه مرة واحدة، وولّده عشوائيًّا
(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")،
واحفظه مع البقية. ولا تتركه أبدًا في مستودع git.
وضع رمز تثبيت#
بين أول إقلاع ومرورك بالمعالج، تكون النسخة لمن يجدها: أول قادم يُنشئ حساب
الإدارة. فضع إذًا INSTALL_TOKEN قبل أن تفتح المنفذ:
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
# ثم في .env:
INSTALL_TOKEN=c3f1…
يقارنه المعالج في زمن ثابت — فمقارنة عادية تتيح تخمين الرمز حرفًا بحرف — ويقيّد كل رفض في السجل. وما إن تُثبَّت النسخة حتى يُغلق المعالج نفسه.
التثبيت بلا واجهة#
للنشر الآلي، يُنشئ ADMIN_EMAIL وADMIN_PASSWORD المسؤولَ عند الإقلاع
دون المرور بالمعالج. ثم انزعهما: فكلمة مرور في بيئة خدمة يقرؤها كل من يقرأ تلك الخدمة.
٣. الصلاحيات وموضع البيانات#
sudo useradd --system --home /var/www/toutadmin --shell /usr/sbin/nologin toutadmin
sudo chown -R toutadmin:toutadmin /var/www/toutadmin/data
sudo chmod 750 /var/www/toutadmin/data
sudo chmod 640 /var/www/toutadmin/.env
يمكن للشفرة أن تبقى للقراءة فقط؛ والذي يجب أن يكون قابلًا للكتابة هو data/ وحده.
ويكتب SQLite ملفات مجاورة (-wal و-shm): فالمهم هو
المجلد، لا القاعدة وحدها.
يكذب NFS وSMB في شأن قفل الملفات. ويعتمد SQLite على ذلك القفل لمنع كتابتين متزامنتين: وعلى مشاركة شبكية تنتهي القاعدة إلى التلف بلا إنذار. قرص محلي دائمًا — والذي يسافر بعيدًا هو النسخة الاحتياطية، لا القاعدة.
٤. الخدمة#
إن شُغّل يدويًّا توقّف المنتج حين تُغلق الطرفية. فسلّمه إلى systemd:
# /etc/systemd/system/toutadmin.service
[Unit]
Description=Toutadmin
After=network.target
[Service]
Type=simple
User=toutadmin
WorkingDirectory=/var/www/toutadmin
EnvironmentFile=/var/www/toutadmin/.env
ExecStart=/usr/bin/node src/server.js
Restart=always
RestartSec=5
# لا تحتاج الخدمة إلى الكتابة إلا في data/.
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/www/toutadmin/data
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now toutadmin
sudo systemctl status toutadmin
journalctl -u toutadmin -f
أسطر التحصين الخمسة ليست زينة: ProtectSystem=strict يجعل نظام الملفات كله غير
قابل للكتابة، وReadWritePaths يعيد فتح المجلد الوحيد الذي يجب أن يكون كذلك. فثغرة
كتابة اعتباطية لا تبلغ حينئذٍ إلا data/.
٥. الوسيط العكسي#
لا تقدّم المنفذ 3000 مباشرةً على الإنترنت أبدًا: فهو لا يقوم بـ TLS، ولا سبب لأن يتعلّمه.
server {
listen 443 ssl http2;
server_name intranet.mithal.com;
ssl_certificate /etc/letsencrypt/live/intranet.mithal.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/intranet.mithal.com/privkey.pem;
client_max_body_size 20M; # الإيداعات في الخزنة، والمستندات الواردة
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name intranet.mithal.com;
return 301 https://$host$request_uri;
}
واجعل الخدمة كذلك تُصغي على الحلقة المحلية وحدها، كي لا يبلغ المنفذَ 3000 إلا الوسيط.
TLS#
sudo certbot --nginx -d intranet.mithal.com
يضع المنتج ترويسة Strict-Transport-Security ما إن يصير الاتصال معمّى، ولا يضعها
في العلن أبدًا: فلو أُعلنت من صفحة غير معمّاة لما قُرئت، ولحبست تجربة محلية في
https ستة أشهر.
TRUST_PROXY يمضي مع الوسيط، ومعه وحده
يجعل هذا المتغير الخادمَ يصدّق عنوان المصدر المعلَن في ترويسة. وخلف nginx هذا هو المطلوب —
وإلا بدت كل الطلبات آتيةً من 127.0.0.1 ولم تعد الحدود لكل عنوان تحمي شيئًا. أما
بلا وسيط في المقدمة فالعكس: يعلن كل امرئ العنوان الذي يشاء ويتجاوز الحدود.
٦. المعالج#
افتح نطاقك. وما دام لا حساب موجودًا، يعيد كل عنوان التوجيه إلى /installation. خمس
خطوات:
- لغة النسخة، من بين 16 لغة.
- المتطلبات، مفحوصةً ومعروضة.
- الشركة: الاسم الذي يظهر في كل مكان.
- الإجازة السنوية الممنوحة لكل موظف جديد غير مستقل.
- حساب الإدارة: عنوان وكلمة مرور من اثني عشر محرفًا على الأقل.
وما إن يوجد حساب حتى يعيد /installation التوجيه إلى صفحة الدخول: فالمعالج أغلق
نفسه، ولا ملف يُحذف يدويًّا.
٧. المسح الدوري#
خلافًا لإصدار PHP، لا شيء يُجدوَل: فالخادم يحمل مجدوِله الخاص، يستيقظ كل ساعة ويفعل ثمانية أشياء.
| ماذا يفعل المسح | الضمانة |
|---|---|
| يغلق الحسابات التي انتهت عقودها | يُفحَص كذلك عند الدخول وعند فتح اللوحة |
| يصدر فواتير الاشتراكات التي حلّ أجلها | فهرس فريد يمنع الفوترة مرتين |
| يحوّل الاستحقاقات إلى إشعارات | مفتاح لمنع التكرار: الاستحقاق ينبّه مرة واحدة |
| ينظّف الإشعارات المقروءة وسجل التدقيق | وفق مدة الحفظ المختارة |
| يسحب صندوق البريد المحاسبي (IMAP) | إن كان السحب مُعدًّا |
| يفرّغ طابور خطافات الويب بإعادات محاولاته | خمس محاولات ثم التخلّي |
| ينشئ النسخة التلقائية ويرسلها خارجًا | متى انقضت المدة الفاصلة |
كل عملية متماثلة القوة: فمسح مُعاد لا يفوتر مرتين ولا يُشعر مرتين. وهذا ما يتيح إعادة تشغيل الخدمة في أي لحظة دون تفكير.
٨. التأكد من أن كل شيء يعمل#
# الاختبارات (لا تكتب في قاعدتك)
npm test
# نسخة عرض للتجوّل فيها
node scripts/seed-demo.js
ثم في الواجهة: أنشئ عضوًا، وادخل به، وأودع مرفقًا، وأطلق نسخة احتياطية يدويًّا وتحقّق منها من شاشة النسخ الاحتياطية. هذه الحركات الأربع تمسّ القاعدة والملفات والصلاحيات والأرشيف.
٩. الترقية#
# ١. نسخة احتياطية أولًا، دائمًا — من شاشة النسخ الاحتياطية
# ٢. الشفرة
cd /var/www/toutadmin
git pull
npm ci --omit=dev
# ٣. أعد التشغيل؛ والقاعدة تحدّث نفسها عند الإقلاع
sudo systemctl restart toutadmin
journalctl -u toutadmin -n 30 --no-pager
يتطوّر المخطط بهجرات متماثلة القوة: فإعادة تشغيل الترقية لا تكسر شيئًا. ولا يُمَسّ
data/ ولا .env أبدًا.
الرجوع إلى الوراء#
أعِد الشفرة إلى الإصدار السابق وأعد التشغيل. والهجرات لا تحذف عمودًا: فقاعدة مُهاجَرة تبقى مقروءة من الإصدار السابق، ما لم يذكر سجل الإصدارات خلاف ذلك. وعند الشك، استعد الأرشيف الذي أخذته في الخطوة الأولى.
١٠. النسخ الاحتياطي حقًّا#
يحمل أرشيف tar.gz القاعدةَ ومجلداتِ الملفات الخمسة. وتُنسخ القاعدة بالنسخ
الاحتياطي المتصل في SQLite، الذي ينتج نسخة متماسكة حتى أثناء كتابة — ويبلغ إصدار PHP النتيجة
نفسها بـVACUUM INTO. ويحمل كل ملف بصمته SHA-256، ويُتحقَّق منها عند الاستعادة.
أرشيف يبقى على الخادم الذي يحميه لا يحمي شيئًا: فاضبط وجهةً بعيدة (FTPS أو Google Drive) من
شاشة النسخ الاحتياطية، واشترك بخطاف ويب على sauvegarde.echec لتعرف متى
يخفق الإرسال إلى الخارج. والتفصيل في صفحة
النسخ الاحتياطي والاستعادة.
جرّبه محليًّا أولًا#
npm install
cp .env.example .env
npm run dev # يعيد التشغيل عند كل تعديل
افتح http://localhost:3000. ومحليًّا اترك NODE_ENV فارغًا: ففي
production تُوسَم الكعكات بأنها «آمنة» ولن تُحفظ على اتصال في العلن — فتدور في
حلقة على صفحة الدخول.
الانتقال إلى إصدار PHP أو القدوم منه#
يتقاسم الإصداران المخطط نفسه — 142 جدولًا و1311 عمودًا — وصيغة
كلمة المرور نفسها. أوقف أحدهما، وانسخ app.sqlite ومجلدات الملفات، وشغّل الآخر:
ولا تحويل هناك. انظر الإصداران.
توثيق Toutadmin — بُني في 2026-09-13. موقع مستقل، منفصل عن البرنامج.