התקנת GitHub Actions runner ב-VPS עם Ubuntu 24.04
מדריך לרישום runner עצמאי ב-Ubuntu 24.04 עם משתמש ייעודי, בדיקת checksum, config.sh ושירות systemd, כולל הסיכון ב-pull request ממאגר מפוצל.
מה עושה runner של GitHub Actions באירוח עצמי
runner של GitHub Actions באירוח עצמי הוא תוכנה שמתקינים ב-VPS שבבעלותכם. התוכנה מבקשת מ-GitHub משימות ומפעילה אותן על החומרה שלכם. רושמים אותה מול מאגר אחד, מתקינים אותה כשירות של systemd, והיא חוזרת לפעול לאחר כל אתחול. GitHub מתזמן את המשימה. השרת שלכם מבצע את העבודה.
CI (אינטגרציה רציפה) במערכת שבבעלותכם משתלם משתי סיבות. דקות הבנייה אינן מחויבות עוד לפי שימוש, ומשימה יכולה לגשת למשאבים שרק למחשב שלכם יש גישה אליהם, כגון מטמון בנייה מחומם או רשת פרטית. המחיר הוא אבטחה. ה-runner מבצע כל פעולה שקובץ ה-workflow מגדיר, תחת המשתמש שהקציתם לו, ולכן קובץ workflow מאפשר ביצוע קוד מרחוק מעצם תכנונו. במאגר פרטי זה תקין, משום שרק אנשים שאתם נותנים בהם אמון יכולים להוסיף קובץ כזה. במאגר ציבורי זהו סיכון ממשי, והסעיף על pull requests ממאגרים מפוצלים מסביר את המנגנון.
כל האמור להלן מתייחס ל-Ubuntu 24.04 ולגרסת runner 2.336.0, שהיא הגרסה הנוכחית נכון ל-July 2026.
מה דרוש לפני שמתחילים
התחילו מ־VPS עם חשבון מנהל רגיל ו־sudo, במצב שאליו מגיעים ב־עשר הדקות הראשונות ב־VPS חדש. אין צורך לפתוח פורט נכנס. ה־runner פותח חיבור HTTPS (פרוטוקול מאובטח להעברת היפרטקסט) יוצא אל GitHub ומשאיר אותו פתוח בזמן שהוא ממתין לעבודה, ולכן GitHub לעולם אינו מתחבר לשרת שלכם. חומת האש יכולה להישאר סגורה בפני העולם, ועדיין יגיעו משימות.
נדרשות לכם גם הרשאות מנהל במאגר, משום שאסימון הרישום מוצג בהגדרות המאגר.
יצירת משתמש ייעודי עבור ה-runner
לעולם אל תפעילו את ה-runner כ-root או כמשתמש המנהלן האישי שלכם. כל משימה יורשת את ההרשאות של משתמש ה-runner, ולכן workflow שמפעיל את sudo יצליח אם משתמש ה-runner יכול להשתמש ב-sudo. צרו משתמש ללא הרשאות מיוחדות, שבבעלותו אין דבר מלבד ספריית הבית שלו. חשבונות משתמשים בהרשאות מזעריות ב-VPS מתאר את התבנית הכללית. להלן התצורה הספציפית.
sudo useradd -m -s /bin/bash gharunner
sudo passwd -l gharunner
sudo chmod 750 /home/gharunner
sudo install -d -m 700 -o gharunner -g gharunner /home/gharunner/actions-runnerpasswd -l נועל את הסיסמה, ולכן איש אינו יכול להתחבר כ-gharunner באמצעותה. ההרשאה 700 על ספריית ה-runner חשובה, משום שה-runner מאחסן שם את פרטי האימות שלו כטקסט גלוי, ו-checkout עשוי להכיל קוד מקור פרטי.
בדקו את שתי התכונות לפני שתמשיכו:
sudo passwd -S gharunner
sudo -l -U gharunnerpasswd -S מדפיס שורה שמתחילה ב-gharunner L, כאשר L מציין שהסיסמה נעולה. sudo -l -U gharunner אמור להחזיר is not allowed to run sudo. אם הוא מדפיס במקום זאת רשימה של פקודות מותרות, החשבון משויך לקבוצת sudo והבידוד שיצרתם בוטל.
הורדת ה-runner ובדיקת קובץ ה-tarball
מכאן יש לעבוד כמשתמש runner.
sudo -iu gharunner
cd ~/actions-runner
RUNNER_VERSION=2.336.0
curl -fL -o actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \
"https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz"הפעילו תחילה את uname -m אם אינכם בטוחים מהי הארכיטקטורה. x86_64 משתמש בקובץ linux-x64 שלמעלה. aarch64 משתמש ב-actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz.
כעת אמתו את הקובץ שהורדתם. ערך ה-SHA256 (אלגוריתם גיבוב מאובטח, באורך 256 סיביות) שלהלן שייך ל-tarball בגרסה 2.336.0 עבור x64. GitHub מציג את הערך של המהדורה הנוכחית בדף המהדורה ובמסך New self-hosted runner. הערך משתנה בכל גרסה, לכן העתיקו אותו משם בעת התקנת גרסה אחרת.
echo "04cf0be1aff4c3ec3554466c39124ca250e3effd8873bb7e8d68535aa9505d5d actions-runner-linux-x64-2.336.0.tar.gz" | sha256sum -cהורדה תקינה מציגה שורה אחת:
actions-runner-linux-x64-2.336.0.tar.gz: OKקובץ שנחתך או שונה מציג הודעת כשל ואזהרה:
actions-runner-linux-x64-2.336.0.tar.gz: FAILED
sha256sum: WARNING: 1 computed checksum did NOT matchאל תדלגו על הבדיקה ואל תאפשרו ל-tar לגלות את הבעיה במקום זאת. ארכיון שנכתב באופן חלקי נכשל עם gzip: stdin: unexpected end of file ו-tar: Unexpected EOF in archive. הדבר מצביע על כך שהקובץ פגום, אך אינו מציין אם ההורדה נקטעה או שהקובץ הוחלף.
tar xzf ./actions-runner-linux-x64-2.336.0.tar.gz
lsמה כוללת חבילת tarball ומה אינה כוללת
לאחר החילוץ, התיקייה מכילה את config.sh, run.sh, env.sh, safe_sleep.sh, bin/ ו-externals/. bin/ מכיל את קובצי ההרצה הבינאריים ואת bin/installdependencies.sh. externals/ מכיל את סביבת ההרצה של Node המצורפת, שבה מופעלות פעולות JavaScript.
svc.sh עדיין אינו קיים. בתיעוד של GitHub הוא מתואר כסקריפט "שנוצר לאחר הוספה מוצלחת של ה-runner", משום שהוא נכתב מתבנית שבה שם המאגר ושם ה-runner משולבים בשם השירות. לכן sudo ./svc.sh install לפני ./config.sh נכשל ומחזיר את sudo: ./svc.sh: command not found. יש לבצע רישום תחילה, ולאחר מכן להתקין את השירות.
התקנת התלויות של ה-runner
ה-runner הוא יישום .NET, ולכן הוא זקוק לכמה ספריות משותפות. יש להישאר במעטפת של משתמש ה-runner ולהתקין אותן באמצעות sudo, משום שהסקריפט כותב למסד הנתונים של חבילות המערכת.
exit
cd /home/gharunner/actions-runner
sudo ./bin/installdependencies.shב-Ubuntu 24.04 פעולה זו מתקינה את libkrb5-3, zlib1g, liblttng-ust1t64, libssl3t64 ואת libicu74. הסקריפט מנסה כמה שמות גרסה עבור כל ספרייה ומשאיר את השם שסביבת ההפצה שלך מספקת. לכן אותו סקריפט פועל גם בגרסאות ישנות יותר של Ubuntu וגם ב-Debian.
אם מדלגים על שלב זה, ./config.sh נעצר לפני שהוא מבצע פעולה כלשהי:
Dependencies is missing for Dotnet Core 6.0
Execute sudo ./bin/installdependencies.sh to install any missing Dotnet Core 6.0 dependencies.חוסר ב-libicu מציג את אותה המלצה תחת שורת פתיחה אחרת, Libicu's dependencies is missing for Dotnet Core 6.0. שתי ההודעות נובעות מאותה סיבה: config.sh מפעיל את ldd מול הספריות הכלולות לפני ההפעלה, ולכן קישור שלא נפתר עוצר את הסקריפט במקום לגרום לקריסה מבלבלת בהמשך.
רישום ה-runner במאגר שלך
קבל אסימון מהמאגר. פתח את Settings, לאחר מכן את Actions, את Runners ולבסוף את New self-hosted runner. הדף מציג אסימון רישום שמתחיל ב-A. תוקפו פג שעה אחת לאחר יצירתו, לכן צור אותו כשאתה מוכן להדביק אותו.
בצע את הרישום כמשתמש של ה-runner. config.sh מסרב לפעול באמצעות sudo.
sudo -iu gharunner
cd ~/actions-runner
./config.sh --url https://github.com/YOUR-USER/YOUR-REPO \
--token PASTE_REGISTRATION_TOKEN_HERE \
--name vps-runner-1 \
--labels vps \
--work _work \
--unattended \
--replaceהסבר על הדגלים. --name הוא השם שבו ה-runner מופיע במאגר, לכן בחר שם שתוכל לזהות גם בעוד שישה חודשים. --labels מוסיף תוויות משלך; ה-runner כבר כולל את self-hosted, Linux ו-X64 בלי שתצטרך לציין זאת. --work מציין את שם התיקייה שבה נשמרות פעולות ה-checkout, בתוך תיקיית ה-runner. --unattended עונה על ההנחיות האינטראקטיביות באמצעות ערכי ברירת המחדל שלהן, וזה רצוי כאשר הפקודה נמצאת בסקריפט. --replace מחליף רישום קיים באותו שם במקום להיכשל, וזה רצוי כאשר אתה בונה מחדש את השרת.
הרצה מוצלחת מסתיימת בשורות האלה:
√ Runner successfully added
√ Runner connection is good
√ Settings Saved.הרישום נמצא כעת בתיקיית ה-runner בתור .runner, .credentials ו-.credentials_rsaparams. שני האחרונים מזהים את ה-runner מול GitHub, לכן כל מי שיכול לקרוא אותם יכול להתחזות אליו. זו הסיבה שמצב התיקייה הוא 700 ולמשתמש אין הרשאות sudo.
התקנת ה-runner כשירות systemd
הפעלת ./run.sh במסוף מתאימה לבדיקה יחידה, אך התהליך מסתיים עם חיבור ה-SSH. התקינו את השירות כדי שה-runner יופעל בעת האתחול. שירותים וטיימרים של systemd ב-VPS מסביר את קובצי היחידה עצמם. כאן svc.sh יוצר עבורכם קובץ כזה.
exit
cd /home/gharunner/actions-runner
sudo ./svc.sh install gharunner
sudo ./svc.sh start
sudo ./svc.sh statussvc.sh דורש הרשאות root מכיוון שהוא כותב יחידה אל /etc/systemd/system ומפעיל אותה. הארגומנט שאחרי install הוא המשתמש שהשירות פועל בשמו. העבירו במפורש את gharunner. ללא ארגומנט, הסקריפט משתמש ב-$SUDO_USER, שהוא חשבון המנהל שלכם, ולכן כל משימה מופעלת כמשתמש שיכול להשתמש ב-sudo.
שם היחידה מורכב משם המאגר ומשם ה-runner, בתבנית actions.runner.YOUR-USER-YOUR-REPO.vps-runner-1.service. אין צורך להקליד אותו:
systemctl list-units 'actions.runner.*'
sudo journalctl -u 'actions.runner.*' -n 20 --no-pagerrunner תקין מתעד את √ Connected to GitHub ולאחר מכן שורה המסתיימת ב-Listening for Jobs, ובדף Runners של המאגר הוא מופיע במצב Idle. runner שמופיע במצב Offline אינו פועל או שאינו יכול להגיע אל GitHub ביציאה 443.
שליחת משימה ל־runner
runs-on בוחר runner לפי תווית. בקשו את self-hosted ואת התווית שלכם, כדי שמשימה לא תופעל ב־runner שלא התכוונתם אליו.
name: build
on:
push:
branches: [main]
jobs:
build:
runs-on: [self-hosted, linux, vps]
steps:
- uses: actions/checkout@v5
- run: uname -aאם המשימה ממתינה ב־Waiting for a runner to pick up this job, התוויות אינן תואמות. כל תווית ב־runs-on חייבת להתקיים ב־runner, ולכן מילה נוספת אחת תשאיר את המשימה בתור בלי להציג שגיאה בשום מקום. השוו את הרשימה לתוויות המוצגות לצד ה־runner בהגדרות המאגר.
מדוע runners בניהול עצמי ומאגרים ציבוריים אינם משתלבים
זהו החלק שאנשים מדלגים עליו. ההנחיות של GitHub חד-משמעיות: יש להשתמש ב-runners בניהול עצמי עבור "מאגרים ציבוריים" כמעט לעולם לא, והם "אינם מספקים ערבויות להפעלה במכונות וירטואליות זמניות ונקיות, ועלולים להיפרץ באופן מתמשך באמצעות קוד לא מהימן ב-workflow".
המנגנון פשוט. pull request מ-fork כולל עותק משלו של קובץ ה-workflow. אם המאגר הציבורי שלך מריץ workflows של pull request ב-runner שלך, כל מי שיכול ליצור fork למאגר יכול להציע workflow שמריץ את הפקודות שלו ב-VPS שלך. אין לו צורך בהרשאת כתיבה, מכיוון שהדבר שהוא מציע הוא הדבר שמופעל.
הגדרות אישור מקלות את הסיכון, אך אינן פותרות אותו. מדיניות ברירת המחדל של מאגר ציבורי מבקשת ממנהל לאשר workflow מ-fork של תורם בפעם הראשונה. לאחר שאישרת את אותו אדם פעם אחת, ה-pull requests הבאים שלו מופעלים ללא בקשת אישור חדשה. לכן השער הוא אדם שקורא diff בכל פעם, וקל לפספס מטען זדוני שמוסתר שלוש רמות בתוך סקריפט build.
pull request מ-fork אינו מקבל את ה-secrets שלך, וה-GITHUB_TOKEN שלו הוא לקריאה בלבד. הדבר מגביל את הנזק בתוך GitHub. הוא אינו עושה דבר כדי להגן על השרת שלך. לתוקף יש shell בשם gharunner, ולכן הוא יכול לקרוא כל קובץ שמשתמש זה יכול לקרוא, להגיע לכל יעד שה-VPS יכול להגיע אליו ברשת הפרטית שלו, ולהשאיר רכיב זדוני ב-~/.bashrc או ביחידת systemd של המשתמש, שתופעל במהלך ה-job הבא.
רישום באמצעות --ephemeral גורם ל-runner לקבל job אחד ולאחר מכן לבטל את הרישום שלו, כך ש-job אחד אינו יכול לקרוא את סביבת העבודה של ה-job הבא. הדבר מועיל רק אם רכיב כלשהו בונה מחדש את המכונה או את ה-container עבור כל job, מכיוון שדלת אחורית שנכתבה בספריית הבית של משתמש ה-runner שורדת רישום חדש.
הכללים הבאים קצרים. השתמש ב-runners בניהול עצמי עבור מאגרים פרטיים. אם אתה חייב לחבר runner כזה למאגר ציבורי, אל תריץ בו pull requests מ-fork, אל תשאיר דבר נוסף בשרת, והתייחס למכונה כאל משאב חד-פעמי.
עבודות Docker והקבוצה שהיא למעשה root
עבודות של קונטיינרים, קונטיינרים של שירות וכל שלב בתהליך עבודה שקורא ל-docker build זקוקים ל-Docker daemon במארח שעליו פועל ה-runner. התקינו את Docker בדרך המקובלת, המתוארת ב-Docker ו-Docker Compose ב-VPS, ולאחר מכן הוסיפו את משתמש ה-runner לקבוצה docker.
הבינו את המשמעות לפני שתעשו זאת. חברות בקבוצה docker שקולה להרשאות root, מכיוון שקונטיינר יכול לבצע bind mount של / ולפעול כ-root בתוכו. לכן תהליך עבודה שיכול לגשת ל-Docker socket יכול לקרוא ולכתוב בכל קובץ ב-VPS, כולל /etc/shadow. במאגר פרטי, שבו כל התורמים מהימנים, ייתכן שזה מחיר מקובל. בכל מקום אחר, הדבר מבטל את היתרון של המשתמש ללא הרשאות מוגברות. Docker ללא הרשאות root משאיר את בניות הקונטיינרים במסגרת ההרשאות של משתמש ה-runner עצמו, במחיר של מנהל אחסון איטי יותר וללא קונטיינרים בעלי הרשאות מוגברות.
עדכונים והסרה תקינה של ה-runner
runner באירוח עצמי מתעדכן כברירת מחדל. הוא מזהה גרסה חדשה, מחליף את הקבצים שלו ומפעיל מחדש את השירות, ולכן בדרך כלל אין צורך לבצע פעולה. ./config.sh --disableupdate משבית את העדכון האוטומטי כאשר נדרשת גרסה קבועה. לאחר מכן האחריות לעדכון מוטלת עליכם: התיעוד של GitHub מציין במפורש ש-runner שהוגדר באמצעות --disableupdate חייב להתעדכן באופן ידני.
עדכון ידני שומר על הרישום, משום ש-.runner ו-.credentials אינם נמצאים בארכיון ה-tarball. עצרו את השירות, הורידו את ה-tarball החדש ובדקו את סכום הביקורת שלו באמצעות gharunner, חלצו אותו על גבי אותה ספרייה באמצעות tar xzf, ולאחר מכן הפעילו שוב את השירות:
cd /home/gharunner/actions-runner
sudo ./svc.sh stop
sudo ./svc.sh startכדי להסיר את ה-runner, הסירו תחילה את השירות ולאחר מכן בטלו את הרישום. אסימון ההסרה מתקבל מאותו דף Runners, תחת הלחצן Remove של ה-runner עצמו.
cd /home/gharunner/actions-runner
sudo ./svc.sh stop
sudo ./svc.sh uninstall
sudo -iu gharunner
cd ~/actions-runner
./config.sh remove --token PASTE_REMOVAL_TOKEN_HEREמחיקת הספרייה ללא ביטול הרישום משאירה את ה-runner ברשימת המאגר במצב Offline, משום ש-GitHub לומד שהוא הוסר רק כאשר ה-runner מודיע על כך או כאשר מנהל מערכת מוחק את הרשומה באופן ידני.
מצבי כשל, עם המחרוזות שתראו
Must not run with sudo. config.sh מדפיס זאת ויוצא כאשר מפעילים אותו כ־root. הבדיקה מכוונת, משום שקבצים בבעלות root בתוך _work משבשים כל משימה מאוחרת יותר שפועלת בשם משתמש השירות. הפעילו את ./config.sh בשם gharunner. המשתנה RUNNER_ALLOW_RUNASROOT עוקף את הבדיקה, אך השימוש בו רק דוחה את הכשל.
sudo: ./svc.sh: command not found. אתם נמצאים בתיקייה הנכונה. svc.sh עדיין אינו קיים, משום ש־config.sh טרם השלים רישום. רשמו את ה־runner ולאחר מכן התקינו את השירות.
Http response code: NotFound from 'POST https://api.github.com/actions/runner-registration'. האסימון אינו אסימון רישום תקף. ייתכן שפג תוקפו, משום שהוא תקף למשך שעה אחת בלבד, או שהודבק personal access token במקום אסימון הרישום מתוך הדף Runners. צרו אסימון חדש והדביקו אותו שוב.
Dependencies is missing for Dotnet Core 6.0. הפעילו את sudo ./bin/installdependencies.sh מתיקיית ה־runner בשם root, ולאחר מכן רשמו אותו שוב.
Runner לא מקוון לאחר אתחול. הפעילו את systemctl is-enabled 'actions.runner.*'. אם לא מופיע דבר, ./svc.sh install מעולם לא הופעל, ולכן ה־runner התקיים רק בתוך סשן הטרמינל שלכם. אם ה־unit מופעל וה־runner עדיין במצב Offline, קראו את journalctl -u 'actions.runner.*' ובדקו חיבורי HTTPS יוצאים.
הדיסק מתמלא. עותקי checkout, מטמוני build ותמונות Docker מצטברים תחת _work ובספריית הבית של משתמש ה־runner, ושום תהליך אינו מנקה אותם עבורכם. עקבו אחר du -sh /home/gharunner/actions-runner/_work והוסיפו ניקוי מתוזמן לפני שהדיסק יפסיק לפעול.
FAQ
מדוע sudo ./svc.sh install מציג את ההודעה command not found?
מכיוון ש-svc.sh אינו נמצא בארכיון ה-tar של ה-runner. הוא נוצר בספריית ה-runner לאחר ש-./config.sh מסיים את הרישום, תוך שימוש בשם המאגר ובשם ה-runner שלך כדי לבנות את שם השירות. הפעל תחילה את ./config.sh בשם המשתמש של ה-runner. לאחר מכן, sudo ./svc.sh install gharunner ימצא את הסקריפט ויכתוב יחידה בשם actions.runner.OWNER-REPO.RUNNER-NAME.service אל /etc/systemd/system.
האם צריך לפתוח יציאה בחומת אש עבור runner באירוח עצמי?
לא. ה-runner פותח חיבור HTTPS יוצא אל GitHub ומשאיר אותו פתוח בזמן שהוא ממתין לעבודות. לכן GitHub אינו יוזם חיבור אל ה-VPS שלך. אפשר תעבורה יוצאת אל 443 והשאר את כללי התעבורה הנכנסת סגורים. אם ה-runner מציג Offline בזמן שהשירות שלו פועל, בדוק סינון תעבורה יוצאת ו-DNS, ולא את כללי התעבורה הנכנסת.
האם אפשר להשתמש ב-runner באירוח עצמי במאגר ציבורי?
כן, אך GitHub ממליצה שלא לעשות זאת. בקשת משיכה ממזלג כוללת קובץ workflow משלה. לכן כל מי שיכול ליצור מזלג למאגר שלך יכול להציע פקודות שיבוצעו במחשב שלך. בקשת האישור חלה רק על ההרצה הראשונה של תורם. אם אתה מחבר runner למאגר ציבורי, השבת בו workflows של בקשות משיכה ממזלגות, אל תשמור דבר נוסף בשרת, ובנה מחדש את המחשב לפי לוח זמנים קבוע.
מדוע הרישום נכשל עם Http response code: NotFound?
קריאת הרישום מחזירה NotFound כאשר פרטי האימות שגויים, ולא רק כאשר הכתובת שגויה. לכן ההודעה עלולה להטעות. אסימוני רישום פוקעים שעה אחת לאחר הצגתם, ו-personal access token אינו מתקבל עבור קריאה זו. פתח שוב את Settings, Actions, Runners, New self-hosted runner, העתק את האסימון החדש, וודא שהערך --url מצביע על מאגר שבו יש לך הרשאות מנהל.