راه‌اندازی سریع سرور اوبونتو: داکر، کلون پروژه از گیت‌هاب و SSL با یک اسکریپت

اسکریپت‌های متن‌بازی که نوشتم تا روی هر سرور Ubuntu یا Debian داکر را با میرورهای ایرانی و کلید GPG تأییدشده نصب کنم، پروژه را بدون جا ماندن توکن روی دیسک از گیت‌هاب کلون کنم و گواهی SSL رایگان بگیرم، حتی wildcard با Cloudflare.

Ali Mahmoodiزمان مطالعه: 10 دقیقهبازدید: 34
راه‌اندازی سریع سرور اوبونتو: داکر، کلون پروژه از گیت‌هاب و SSL با یک اسکریپت

هر بار که یک VPS تازه تحویل می‌گیرم، تقریباً همان کارها را از اول تکرار می‌کنم: نصب داکر، گرفتن پروژه از گیت‌هاب، و بعد گرفتن گواهی SSL برای دامنه. روی کاغذ کار ساده‌ای است، ولی روی سرورهای داخل ایران همیشه یک جای کار می‌لنگد. یک بار download.docker.com به خاطر تحریم باز نمی‌شود، یک بار میرور موردعلاقه‌ام 503 می‌دهد، و یک بار هم چند ماه بعد می‌بینم لاگ‌های داکر دیسک را پر کرده‌اند.

برای همین این کارها را در چند اسکریپت bash جمع کردم و در ریپوی عمومی intial_ubuntu_server روی گیت‌هاب گذاشتم. در این مقاله توضیح می‌دهم هر اسکریپت دقیقاً چه کاری می‌کند، چطور اجرایش کنید و کجاها باید حواستان باشد.

داخل ریپو چه چیزی هست؟

ریپو چهار اسکریپت دارد که هر کدام جدا اجرا می‌شوند:

  • install-docker.sh: نصب Docker Engine و Compose، با میرورهای ایرانی و چند مسیر جایگزین
  • clone-repo.sh: نصب Git و کلون کردن یک ریپوی عمومی یا خصوصی گیت‌هاب در مسیر /opt
  • install-ssl.sh: گرفتن گواهی Let's Encrypt با Certbot و راه‌اندازی تمدید خودکار
  • tunnel-install.sh: نصب کلاینت SoftEther VPN و OpenVPN

چند چیز بین همه‌ی اسکریپت‌ها مشترک است:

  • اجرا با root: اگر با root اجرا نشوند، متوقف می‌شوند.
  • چک سیستم‌عامل و نسخه: فقط روی Ubuntu 20.04 به بالا و Debian 11 به بالا اجرا می‌شوند. نسخه‌های testing و sid دبیان که شماره‌ی نسخه ندارند، با یک هشدار اجازه‌ی اجرا دارند.
  • سؤال‌ها از ترمینال خوانده می‌شوند: همه‌ی سؤال‌ها مستقیم از /dev/tty خوانده می‌شوند، نه از ورودی استاندارد. برای همین حتی اگر اسکریپت را با curl ... | sudo bash اجرا کنید، جواب‌ها را از کیبورد شما می‌گیرد. اگر ترمینالی در کار نباشد (مثلاً داخل cron یا CI)، اسکریپت به‌جای حدس زدن جواب‌ها، با یک پیغام خطای واضح متوقف می‌شود.

ریپو با لایسنس MIT منتشر شده است.

مرحله‌ی اول: نصب داکر با install-docker.sh

اگر داکر از قبل نصب و در حال اجرا باشد، اسکریپت اول می‌پرسد نصب کامل را رد کند و فقط daemon.json را تنظیم کند یا نه. پیش‌فرض و پیشنهاد من همین گزینه است. بعد می‌پرسد سرور داخل ایران است یا خارج از ایران و این کارها را انجام می‌دهد:

  1. پیش‌نیازها را نصب می‌کند: ca-certificates، curl، gnupg و چند بسته‌ی دیگر.
  2. در حالت نصب کامل، نسخه‌های قدیمی داکر را حذف می‌کند.
  3. کلید GPG داکر را می‌گیرد. اول از سایت رسمی داکر امتحان می‌کند و اگر نشد، از میرورهای IranServer، MobinHost، Kernel و ArvanCloud. کلید، از هر جایی که بیاید، فقط وقتی قبول می‌شود که fingerprint آن با کلید رسمی داکر یکی باشد (9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88). این‌طوری هیچ میروری نمی‌تواند کلید خودش را جای کلید داکر جا بزند. اگر از هیچ منبعی کلید معتبر پیدا نشود، مخزن‌های docker-ce کنار گذاشته می‌شوند و فقط بسته‌ی docker.io خود توزیع امتحان می‌شود که امضای Ubuntu یا Debian را دارد.
  4. در حالت ایران، میرورها را به این ترتیب امتحان می‌کند: IranServer، MobinHost، Kernel، Shatel و ArvanCloud. برای هر میرور اول فایل InRelease را چک می‌کند و بسته‌ها را از اولین میروری که جواب بدهد نصب می‌کند. اگر همه شکست بخورند، سراغ مخزن رسمی داکر می‌رود و در آخر سراغ بسته‌ی docker.io توزیع.
  5. بسته‌های docker-ce، docker-ce-cli، containerd.io، docker-buildx-plugin و docker-compose-plugin را نصب می‌کند.
  6. daemon.json مورد نظر را می‌سازد. در این فایل چرخش لاگ تنظیم شده، یعنی هر فایل لاگ حداکثر ۱۰ مگابایت و حداکثر ۳ فایل. در حالت ایران، رجیستری‌های docker.iranserver.com، docker.kernel.ir، docker.arvancloud.ir و focker.ir هم اضافه می‌شوند. فایل فعلی فقط وقتی عوض می‌شود که محتوایش واقعاً فرق کرده باشد، و قبلش از آن بکاپ گرفته می‌شود.
  7. داکر را فقط وقتی کانفیگ عوض شده باشد ری‌استارت می‌کند. اگر کانتینری در حال اجرا باشد، اول از شما اجازه می‌گیرد (پیش‌فرض: نه) و هشدار می‌دهد که ری‌استارت کانتینرها را متوقف می‌کند و کانتینرهای بدون restart policy خاموش می‌مانند.
  8. نسخه‌ی Docker و Compose را چک می‌کند و hello-world را اجرا می‌کند.

نتیجه این است که اجرای دوباره‌ی اسکریپت روی سروری که سرویس زنده دارد، دیگر بی‌دلیل چیزی را خاموش نمی‌کند.

مرحله‌ی دوم: اتصال به پروژه‌ی گیت‌هاب با clone-repo.sh

این اسکریپت Git را (اگر نصب نباشد) همراه با curl، wget و unzip نصب می‌کند و بعد چهار سؤال می‌پرسد: نام کاربری گیت‌هاب، نام ریپو، برنچ (پیش‌فرض main) و اینکه ریپو خصوصی است یا نه. پروژه در /opt/<نام-ریپو> کلون می‌شود. اگر این پوشه از قبل وجود داشته باشد، اسکریپت قبل از پاک کردنش از شما می‌پرسد.

برای ریپوی خصوصی، اسکریپت یک Personal Access Token می‌خواهد و با توکن رفتار امنی دارد:

  • موقع تایپ، توکن روی صفحه نمایش داده نمی‌شود و در هیچ خروجی‌ای هم نمی‌آید.
  • بعد از کلون، آدرس remote به آدرس تمیز https://github.com/<user>/<repo>.git برگردانده می‌شود، پس توکن در .git/config نمی‌ماند.
  • اگر بخواهید بعداً بدون وارد کردن دوباره‌ی توکن git pull بزنید، اسکریپت پیشنهاد می‌دهد توکن را در یک فایل جداگانه به اسم /root/.git-credentials-<repo> با دسترسی 600 ذخیره کند. این فایل فقط برای همان یک ریپو تنظیم می‌شود.
  • متغیر توکن بعد از استفاده پاک می‌شود. اسکریپت پیشنهاد می‌دهد به‌جای توکن classic با دسترسی کامل repo، از یک fine-grained token فقط‌خواندنی برای همان ریپو یا از SSH Deploy Key استفاده کنید.

اگر کلون با برنچی که گفتید شکست بخورد، یک بار هم با برنچ پیش‌فرض ریپو امتحان می‌کند. اگر در ریشه‌ی پروژه docker-compose.yml یا compose.yml باشد، می‌پرسد پروژه را با docker compose up -d بالا بیاورد یا نه.

مرحله‌ی سوم: گرفتن SSL با install-ssl.sh

این اسکریپت Certbot را (اگر نصب نباشد) نصب می‌کند، دامنه و ایمیل را می‌گیرد و بعد پنج روش اعتبارسنجی پیشنهاد می‌دهد:

  1. Standalone: خود Certbot موقتاً روی پورت 80 یک وب‌سرور بالا می‌آورد. فقط در این روش پورت 80 چک می‌شود. اگر nginx، apache2، httpd یا یک کانتینر داکر پورت را گرفته باشد، اسکریپت پیشنهاد می‌دهد موقتاً متوقفش کند. بعد از کار، چه گرفتن گواهی موفق باشد چه نه، آن سرویس دوباره روشن می‌شود. pre-hook و post-hook هم در کانفیگ Certbot ذخیره می‌شوند تا تمدیدهای بعدی همان سرویس را خودشان قطع و وصل کنند.
  2. Webroot: فایل‌های چالش در webroot وب‌سرورتان گذاشته می‌شوند. وب‌سرور روشن می‌ماند.
  3. Nginx: Certbot خودش کانفیگ nginx را تنظیم می‌کند. اگر پلاگین python3-certbot-nginx نصب نباشد، اسکریپت نصبش می‌کند. nginx روشن می‌ماند.
  4. DNS دستی: با یک رکورد TXT که خودتان ثبت می‌کنید، و گواهی wildcard (*.domain.com) را هم پشتیبانی می‌کند. این حالت تعاملی اجرا می‌شود تا Certbot مقدار رکورد را نشانتان بدهد و صبر کند. اسکریپت صریحاً هشدار می‌دهد که این گواهی خودکار تمدید نمی‌شود.
  5. DNS از طریق Cloudflare: اگر DNS دامنه‌تان روی Cloudflare است، این بهترین گزینه برای wildcard است. اسکریپت بسته‌ی python3-certbot-dns-cloudflare را نصب می‌کند، توکن API را بدون نمایش روی صفحه می‌گیرد و در /root/.secrets/certbot/cloudflare.ini با دسترسی 600 ذخیره می‌کند. گواهی هم کاملاً خودکار تمدید می‌شود. توکن را در داشبورد Cloudflare با قالب «Edit zone DNS» و فقط برای همان zone بسازید.

دستور Certbot با --cert-name <domain> اجرا می‌شود تا گواهی همیشه در /etc/letsencrypt/live/<domain>/ باشد. بعد از گرفتن گواهی، اسکریپت تمدید خودکار را چک می‌کند. اگر تایمر systemd خود Certbot فعال نباشد، یک کران‌جاب در /etc/cron.d/certbot-renew می‌سازد که روزی دو بار تمدید را امتحان می‌کند. در آخر هم فقط برای همین گواهی certbot renew --dry-run می‌زند. برای گواهی DNS دستی این تست انجام نمی‌شود، چون بدون هوک شکست می‌خورد.

پیش‌نیازها

  • سرور Ubuntu 20.04 به بالا یا Debian 11 به بالا
  • دسترسی root و یک نشست SSH تعاملی، چون اسکریپت‌ها سؤال می‌پرسند
  • اینترنت برای دسترسی به گیت‌هاب، میرورهای داکر و Let's Encrypt
  • برای SSL با روش‌های Standalone، Webroot و Nginx: رکورد A دامنه باید به IP سرور اشاره کند و پورت 80 از اینترنت در دسترس باشد. اسکریپت به پورت 443 کاری ندارد، ولی برای سرو کردن HTTPS باید بعداً آن را در فایروال باز کنید.
  • برای روش Cloudflare: یک API Token با دسترسی Zone:DNS:Edit
  • برای ریپوی خصوصی: یک Personal Access Token گیت‌هاب یا Deploy Key

شروع سریع

همه‌ی اسکریپت‌ها را می‌شود بدون کلون کردن ریپو و مستقیم از گیت‌هاب اجرا کرد. ترتیب منطقی این است:

# ۱. نصب داکر
sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/alpha330/intial_ubuntu_server/main/install-docker.sh)"

# ۲. کلون پروژه از گیت‌هاب در /opt
sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/alpha330/intial_ubuntu_server/main/clone-repo.sh)"

# ۳. گرفتن گواهی SSL
sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/alpha330/intial_ubuntu_server/main/install-ssl.sh)"

چون سؤال‌ها از ترمینال خوانده می‌شوند، شکل pipe هم کار می‌کند:

curl -fsSL https://raw.githubusercontent.com/alpha330/intial_ubuntu_server/main/install-docker.sh | sudo bash

روی سرور production، امن‌ترین کار این است که اول کد را بخوانید و بعد اجرا کنید:

curl -fsSL -o install-docker.sh https://raw.githubusercontent.com/alpha330/intial_ubuntu_server/main/install-docker.sh
less install-docker.sh
sudo bash install-docker.sh

برنچ main هر لحظه ممکن است تغییر کند. اگر می‌خواهید همیشه دقیقاً همان کدی را اجرا کنید که بررسی کرده‌اید، به‌جای main در آدرس، SHA یک کامیت را بگذارید. مثلاً برای نسخه‌ای که این مقاله بر اساسش نوشته شده:

sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/alpha330/intial_ubuntu_server/6bec12ecbd6f6f08e9f4d2d19618797cc36b45f7/install-docker.sh)"

شکل sudo bash <(curl ...) را استفاده نکنید، چون sudo فایل‌دیسکریپتور process substitution را می‌بندد. بعد از نصب هم می‌توانید با این دستورها همه‌چیز را چک کنید:

docker --version
docker compose version
sudo certbot certificates
sudo certbot renew --dry-run

شخصی‌سازی

کد اسکریپت‌ها ساده و خواناست و اگر ریپو را fork کنید، شخصی‌سازی‌شان راحت است:

  • ترتیب میرورها: آرایه‌ی IRAN_MIRRORS در install-docker.sh. میروری را که برای دیتاسنتر شما بهتر جواب می‌دهد بالاتر بگذارید.
  • رجیستری‌ها و چرخش لاگ: بخش ساخت daemon.json، که مقدارهای max-size و max-file هم همان‌جاست.
  • مسیر کلون: متغیر TARGET_DIR در clone-repo.sh که پیش‌فرضش /opt/<repo> است.
  • هوک بعد از تمدید SSL: گزینه‌ی --deploy-hook در کران‌جاب. اگر وب‌سرورتان داخل داکر است، systemctl reload nginx را با دستوری مثل docker compose restart عوض کنید.
  • سرویس‌دهنده‌ی DNS دیگر: برای Route53، DigitalOcean و بقیه، پلاگین Certbot همان سرویس را نصب کنید (مثلاً python3-certbot-dns-route53) و Certbot را مستقیم با آن اجرا کنید.
  • اگر fork کردید، آدرس raw.githubusercontent.com را در دستورهای بالا به آدرس ریپوی خودتان تغییر دهید.

اسکریپت چهارم: tunnel-install.sh

این اسکریپت معماری سیستم را تشخیص می‌دهد (amd64، arm64، i386 و armhf) و نسخه‌ی v4.44 کلاینت SoftEther VPN را از ریلیزهای رسمی گیت‌هاب SoftEther دانلود می‌کند. روی amd64 و arm64، هش SHA256 فایل را با مقدار ثابت داخل اسکریپت مقایسه می‌کند و اگر یکی نباشد، نصب را متوقف می‌کند. روی i386 و armhf هش ثابتی وجود ندارد، پس ادامه دادن تأیید صریح شما را لازم دارد و پیش‌فرضش لغو است.

بعد از آن، کلاینت را کامپایل می‌کند و در /usr/local/vpnclient نصب می‌کند، یک سرویس systemd به اسم vpnclient می‌سازد و OpenVPN را نصب می‌کند. پلاگین Network Manager فقط روی دسکتاپ به کار می‌آید، برای همین نصبش اختیاری است و پیش‌فرضش «نه» است.

نکات امنیتی و محدودیت‌هایی که هنوز هست

بیشتر مشکلاتی که در نسخه‌های اول داشتم حالا برطرف شده‌اند. این چند مورد هنوز باقی‌اند و بهتر است بدانید:

  • root و آخرین نسخه: همه‌ی اسکریپت‌ها با root اجرا می‌شوند و دستورهای بالا آخرین نسخه‌ی main را می‌گیرند. روی production کد را اول بخوانید یا روی یک SHA کامیت قفلش کنید.
  • checksum روی i386 و armhf: SoftEther برای این معماری‌ها هش رسمی منتشر نمی‌کند، پس دانلود فقط با تأیید خود شما و بدون بررسی هش ادامه پیدا می‌کند.
  • تمدید گواهی DNS دستی: گواهی روش ۴ خودکار تمدید نمی‌شود. یا قبل از ۹۰ روز اسکریپت را دوباره اجرا کنید، یا از روش ۵ (Cloudflare) یا پلاگین DNS سرویس‌دهنده‌ی خودتان استفاده کنید.
  • اجرای دوباره‌ی اسکریپت SSL: هر بار اجرا، گواهی را دوباره از Let's Encrypt درخواست می‌کند. Let's Encrypt برای تعداد گواهی‌ها محدودیت دارد، پس برای تست از certbot renew --dry-run استفاده کنید، نه از اجرای مکرر اسکریپت.
  • کران‌جاب تمدید: کران‌جاب فقط nginx نصب‌شده روی خود سرور را reload می‌کند. اگر وب‌سرورتان داخل کانتینر است، هوک را مطابق بخش شخصی‌سازی عوض کنید.

چند نکته برای رفع اشکال

  • No terminal (TTY) available for interactive prompts: اسکریپت را بدون ترمینال اجرا کرده‌اید، مثلاً از cron یا CI. از یک نشست SSH تعاملی اجرایش کنید.
  • curl: (7) Failed to connect to download.docker.com: اسکریپت را دوباره اجرا کنید و گزینه‌ی ۱ (ایران) را بزنید. اگر همه‌ی روش‌ها شکست خوردند، اسکریپت پیشنهاد می‌دهد DNS سرور را به 178.22.122.100 (شکن) تغییر دهید.
  • does NOT match Docker's official fingerprint: میروری که امتحان شده کلید دیگری داده و کلیدش رد شده است. اسکریپت خودش سراغ منبع بعدی می‌رود.
  • خطای [[: not found: اسکریپت را با sh اجرا کرده‌اید. حتماً از bash استفاده کنید.
  • Repository not found: نام کاربری و نام ریپو را دوباره چک کنید. برای ریپوی خصوصی، توکن باید به همان ریپو دسترسی داشته باشد و منقضی نشده باشد.
  • Could not bind to port 80: در روش Standalone اجازه بدهید اسکریپت سرویس روی پورت 80 را موقتاً متوقف کند، یا از روش Webroot یا Nginx استفاده کنید.
  • مشکل در تمدید گواهی: لاگ را در /var/log/letsencrypt/letsencrypt.log ببینید و certbot renew --dry-run --verbose را اجرا کنید.

جمع‌بندی

intial_ubuntu_server قرار نیست جای Ansible یا Terraform را بگیرد. یک جعبه‌ابزار کوچک و خواناست که کارهای تکراری راه‌اندازی سرور را در چند دقیقه انجام می‌دهد و مشکلات خاص سرورهای ایران مثل تحریم و میرورهای ناپایدار را هم در نظر می‌گیرد. در آخرین نسخه، امنیت و قابل اطمینان بودنش هم بیشتر شده است: کلید GPG داکر بررسی می‌شود، توکن روی دیسک نمی‌ماند، اجرای دوباره چیزی را بی‌دلیل خاموش نمی‌کند و دستور یک‌خطی با pipe هم درست کار می‌کند. من خودم الان هر سرور تازه را با همین سه دستور آماده می‌کنم: داکر، کلون پروژه و SSL.

کد کامل در گیت‌هاب است: github.com/alpha330/intial_ubuntu_server. اگر به کارتان آمد، ستاره بدهید، و اگر باگی دیدید یا ایده‌ای داشتید، Issue یا Pull Request باز کنید.

مقالات مرتبط

نظرات

در حال بارگذاری نظرات...