התקנת GitHub Actions Runner על שרת VPS עם Ubuntu 24.04
מדריך מעשי להגדרת GitHub Actions runner עצמאי על Ubuntu 24.04. כולל יצירת משתמש ייעודי, בדיקת checksum, הרצה כשירות systemd והסבר על סיכוני אבטחה ב-fork pull requests.
מה עושה runner של GitHub Actions בהתקנה עצמית
Runner של GitHub Actions בהתקנה עצמית הוא תוכנה שמתקינים על ה-VPS שלכם, הפונה ל-GitHub כדי למשוך משימות ולהריץ אותן על החומרה שלכם. אתם רושמים אותו מול מאגר (repository) ספציפי, מתקינים אותו כשירות systemd, והוא עולה מחדש לאחר כל אתחול. GitHub מתזמנת את המשימה, והשרת שלכם מבצע את העבודה.
CI (אינטגרציה רציפה) על שרת בבעלותכם כדאית משתי סיבות. דקות הבנייה מפסיקות להימדד, ומשימה יכולה לגשת למשאבים שזמינים רק למכונה שלכם, כמו מטמון בנייה (build cache) חם או רשת פרטית. המחיר הוא אבטחה. ה-runner מבצע כל מה שמוגדר בקובץ ה-workflow, תחת המשתמש שהגדרתם לו; לכן, קובץ workflow הוא למעשה ביצוע קוד מרחוק (remote code execution) מעצם תכנונו. במאגר פרטי זה תקין, כיוון שרק אנשים שאתם סומכים עליהם יכולים להוסיף קובץ כזה. במאגר ציבורי מדובר בסיכון ממשי, והסעיף העוסק ב-fork pull requests מסביר את המנגנון.
כל המפורט להלן מבוסס על Ubuntu 24.04 עם גרסת runner מספר 2.336.0, שהיא הגרסה העדכנית נכון ליולי 2026.
מה דרוש לך לפני תחילת העבודה
התחל משרת VPS עם חשבון מנהל רגיל והרשאות sudo, המצב אליו מגיעים ב-עשר הדקות הראשונות בשרת VPS חדש. אין צורך לפתוח פורט נכנס. ה-runner פותח חיבור HTTPS יוצא אל GitHub ומחזיק אותו פתוח בזמן שהוא ממתין למשימות, לכן GitHub לעולם לא מתחבר לשרת שלך ישירות. ה-firewall שלך יכול להישאר סגור לעולם החיצון, והמשימות יגיעו בכל זאת.
עליך להחזיק גם בהרשאות ניהול במאגר (repository), כיוון ש-token הרישום מוצג בהגדרות המאגר.
יצירת משתמש ייעודי עבור ה-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-runnerהפקודה passwd -l נועלת את הסיסמה, כך שאף אחד לא יוכל להתחבר כ-gharunner באמצעותה. הגדרת מצב 700 על ספריית ה-runner היא קריטית, כיוון שה-runner מאחסן שם את פרטי ההזדהות שלו בטקסט גלוי, ופעולת checkout עלולה להכיל קוד מקור פרטי.
בדקו את שני המאפיינים הללו לפני שתמשיכו:
sudo passwd -S gharunner
sudo -l -U gharunnerהפקודה passwd -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 מציגה את הערך עבור הגרסה הנוכחית בדף ה-release ובמסך ה-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/ מכילה את קובצי ה-binary של ה-runner ואת bin/installdependencies.sh. externals/ מכילה את סביבת ה-Node המצורפת שעליה מורצים ה-JavaScript actions.
עדיין לא קיים svc.sh. התיעוד של GitHub מתאר אותו כסקריפט "שנוצר לאחר הוספה מוצלחת של ה-runner", כיוון שהוא נכתב מתוך תבנית שבה שם המאגר ושם ה-runner מוטמעים בתוך שם השירות. לכן, הרצה של sudo ./svc.sh install לפני ./config.sh תיכשל עם sudo: ./svc.sh: command not found. יש לבצע רישום תחילה, ורק לאחר מכן להתקין את השירות.
התקנת התלויות של ה-runner
ה-runner הוא יישום .NET, ולכן הוא זקוק למספר ספריות משותפות. צאו מה-shell של משתמש ה-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 מול המאגר שלכם
השיגו אסימון (token) מהמאגר. פתחו את 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 מוסיף תוויות (labels) משלכם; ה-runner נושא ממילא את self-hosted, Linux ו-X64 ללא צורך בהגדרה נוספת. --work מגדיר את שם הספרייה שבה יבוצעו ה-checkouts, בתוך ספריית ה-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 בטרמינל מתאים לבדיקה חד-פעמית, אך התהליך יסתיים עם סגירת ה-session של ה-SSH. התקינו את השירות כדי שה-runner יעלה אוטומטית בעת עליית השרת. המדריך systemd services and timers on a VPS מסביר על קובצי ה-unit עצמם. כאן svc.sh יוצר אחד עבורכם.
exit
cd /home/gharunner/actions-runner
sudo ./svc.sh install gharunner
sudo ./svc.sh start
sudo ./svc.sh statussvc.sh דורש הרשאות root כיוון שהוא כותב קובץ unit לתוך /etc/systemd/system ומפעיל אותו. הארגומנט אחרי install הוא המשתמש שתחתיו ירוץ השירות. העבירו את gharunner במפורש. ללא ארגומנט, הסקריפט יחזור להשתמש ב-$SUDO_USER, שהוא חשבון ה-admin שלכם, ואז כל משימה תרוץ כמשתמש בעל הרשאות sudo.
ה-unit נקרא על שם ה-repository וה-runner, בתבנית actions.runner.YOUR-USER-YOUR-REPO.vps-runner-1.service. לעולם לא תצטרכו להקליד זאת ידנית:
systemctl list-units 'actions.runner.*'
sudo journalctl -u 'actions.runner.*' -n 20 --no-pager-runner תקין מתעד √ Connected to GitHub ולאחר מכן שורה שמסתיימת ב-Listening for Jobs, ודף ה-Runners של ה-repository יציג אותו כ-Idle. runner שמוצג כ-Offline אינו רץ או שאינו מצליח להגיע ל-GitHub בפורט 443.
שליחת משימה ל-runner
runs-on בוחר runner לפי תווית (label). בקשו את 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 בהגדרות ה-repository.
מדוע אין להשתמש ב-runners בניהול עצמי עבור מאגרים ציבוריים
זהו החלק שאנשים נוטים לדלג עליו. ההנחיות של GitHub חד-משמעיות: אין להשתמש ב-runners בניהול עצמי עבור מאגרים ציבוריים כמעט בשום מקרה. הם אינם מבטיחים הרצה בתוך מכונות וירטואליות נקיות וזמניות, ועלולים להיפרץ לצמיתות על ידי קוד לא מהימן בתוך workflow.
המנגנון פשוט. בקשת משיכה (pull request) מ-fork מביאה איתה עותק של קובץ ה-workflow. אם המאגר הציבורי שלכם מריץ workflows של בקשות משיכה על ה-runner שלכם, כל מי שיכול לבצע fork למאגר יכול להציע workflow שמריץ פקודות משלו על ה-VPS שלכם. הם אינם זקוקים להרשאות כתיבה, כיוון שהקוד שהם מציעים הוא הקוד שמתבצע בפועל.
הגדרות אישור מקלות על הבעיה אך לא פותרות אותה. מדיניות ברירת המחדל עבור מאגר ציבורי דורשת ממנהל המאגר לאשר workflow של תורם חדש. לאחר שאישרתם את אותו אדם פעם אחת, בקשות המשיכה הבאות שלו ירוצו ללא התראה נוספת. כך שהחסם נשאר קריאה אנושית של ה-diff בכל פעם מחדש, וקל מאוד לפספס קוד זדוני שמוסתר עמוק בתוך סקריפט בנייה.
בקשת משיכה מ-fork אינה מקבלת גישה ל-secrets שלכם, וה-GITHUB_TOKEN שלה הוא לקריאה בלבד. זה מגביל את הנזק בתוך GitHub, אך לא עושה דבר עבור השרת שלכם. לתוקף יש גישת shell כ-gharunner, ולכן הוא יכול לקרוא כל קובץ שהמשתמש הזה יכול לקרוא, להגיע לכל יעד שה-VPS יכול להגיע אליו ברשת הפרטית שלו, ולהשאיר עקבות ב-~/.bashrc או ביחידת systemd של המשתמש שתרוץ בעבודה הבאה.
רישום באמצעות --ephemeral גורם ל-runner לקבל עבודה אחת ואז להתנתק, כך שעבודה אחת לא יכולה לקרוא את סביבת העבודה של העבודה הבאה. זה עוזר רק אם משהו מקים מחדש את המכונה או את המכולה עבור כל עבודה, כיוון שדלת אחורית שנכתבה לתיקיית הבית של משתמש ה-runner תשרוד רישום מחדש.
הכללים הבאים קצרים. השתמשו ב-runners בניהול עצמי עבור מאגרים פרטיים בלבד. אם אתם חייבים לחבר אחד למאגר ציבורי, אל תריצו עליו בקשות משיכה מ-fork, אל תשמרו שום דבר אחר על השרת הזה, והתייחסו למכונה כאל משאב מתכלה.
משימות Docker, והקבוצה שהיא למעשה root
משימות מכולה (container jobs), מכולות שירות וכל שלב ב־workflow הקורא ל-docker build זקוקים ל-Docker daemon על מארח ה-runner. התקינו את Docker בדרך המקובלת, כפי שמתואר ב-Docker and Docker Compose on a VPS, ולאחר מכן הוסיפו את משתמש ה-runner לקבוצת docker.
הבינו את המשמעות לפני ביצוע הפעולה. חברות בקבוצת docker שקולה להרשאות root, כיוון שמכולה יכולה לבצע bind mount ל-/ ולהריץ תהליכים כ-root בתוכה. לכן, workflow שיכול לתקשר עם ה-Docker socket מסוגל לקרוא ולכתוב כל קובץ ב-VPS, כולל /etc/shadow. במאגר פרטי עם תורמים מהימנים, ייתכן שזהו מחיר קביל. בכל מקרה אחר, הדבר מבטל את הערך של שימוש במשתמש ללא הרשאות מיוחדות. Rootless Docker שומר על בניית מכולות במסגרת ההרשאות של משתמש ה-runner עצמו, במחיר של מנהל אחסון איטי יותר וחוסר יכולת להריץ מכולות מועדפות (privileged containers).
עדכונים והסרת ה-runner בצורה נקייה
ה-runner המארח את עצמו מתעדכן כברירת מחדל. הוא מזהה גרסה חדשה, מחליף את הקבצים שלו ומפעיל מחדש את השירות, כך שבדרך כלל אין צורך בפעולה מצדך. הדגל ./config.sh --disableupdate מכבה את העדכון האוטומטי כאשר יש צורך בגרסה קבועה. לאחר מכן, העדכון הוא באחריותך: התיעוד של GitHub מבהיר ש-runner שהוגדר עם --disableupdate חייב לעבור עדכון ידני.
עדכון ידני שומר על הרישום, כיוון ש-.runner ו-.credentials אינם נמצאים בתוך ה-tarball. עצור את השירות, הורד ובצע בדיקת checksum ל-tarball החדש כ-gharunner, חלץ אותו על אותה ספרייה בעזרת tar xzf, ולאחר מכן הפעל את השירות מחדש:
cd /home/gharunner/actions-runner
sudo ./svc.sh stop
sudo ./svc.sh startכדי להסיר את ה-runner, הסר תחילה את השירות ולאחר מכן בצע ביטול רישום (deregister). אסימון ההסרה (removal token) מגיע מאותו דף 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'. ה-token אינו token רישום תקין. ייתכן שהוא פג תוקף, שכן תוקפם מוגבל לשעה, או שהודבק token גישה אישי (personal access token) במקום ה-token שמופיע בדף ה-Runners. יש להנפיק token חדש ולהדביק אותו שוב.
Dependencies is missing for Dotnet Core 6.0. יש להריץ את sudo ./bin/installdependencies.sh מתוך תיקיית ה-runner כ-root, ולאחר מכן לבצע רישום מחדש.
ה-runner במצב Offline לאחר אתחול. יש להריץ את systemctl is-enabled 'actions.runner.*'. אם לא מופיעה רשימה, סימן ש-./svc.sh install מעולם לא הופעל, וה-runner היה קיים רק בתוך סשן הטרמינל שלכם. אם היחידה מוגדרת כ-enabled וה-runner עדיין במצב Offline, יש לקרוא את journalctl -u 'actions.runner.*' ולבדוק תקשורת HTTPS יוצאת.
הדיסק מתמלא. קובצי checkout, מטמון בנייה (build caches) ואימג'ים של Docker מצטברים תחת _work ובתיקיית הבית של משתמש ה-runner, ואין תהליך שמנקה אותם עבורכם. יש לנטר את du -sh /home/gharunner/actions-runner/_work ולהוסיף משימת ניקוי מתוזמנת לפני שהדיסק יתמלא ויעצור את הפעילות.
FAQ
מדוע sudo ./svc.sh install מציג את השגיאה command not found?
הסיבה היא ש-svc.sh אינו כלול ב-tarball של ה-runner. הקובץ נוצר בספריית ה-runner לאחר ש-./config.sh מסיים את תהליך הרישום, תוך שימוש בשם המאגר ובשם ה-runner כדי לבנות את שם השירות. יש להריץ את ./config.sh תחילה כמשתמש ה-runner. לאחר מכן, sudo ./svc.sh install gharunner ימצא את הסקריפט ויכתוב יחידת שירות בשם actions.runner.OWNER-REPO.RUNNER-NAME.service לתוך /etc/systemd/system.
האם עליי לפתוח פורט ב-firewall עבור runner מאוחסן עצמית?
לא. ה-runner פותח חיבור HTTPS יוצא אל GitHub ומחזיק אותו פתוח בזמן שהוא ממתין למשימות, לכן GitHub לעולם לא יוזם חיבור אל ה-VPS שלך. אפשרו תעבורה יוצאת בפורט 443 והשאירו את חוקי התעבורה הנכנסת סגורים. אם ה-runner מציג מצב Offline בזמן שהשירות שלו רץ, בדקו סינון תעבורה יוצאת ו-DNS במקום לבדוק חוקי כניסה.
האם ניתן להשתמש ב-runner מאוחסן עצמית במאגר ציבורי?
ניתן, אך GitHub ממליצה שלא לעשות זאת. בקשת Pull request מ-fork נושאת איתה את קובץ ה-workflow שלה, כך שכל מי שיכול לבצע fork למאגר שלך יכול להציע פקודות שירוצו על המכונה שלך. הנחיית האישור מכסה רק את ההרצה הראשונה של תורם. אם אתם מחברים runner למאגר ציבורי, השביתו בו workflows של fork pull requests, אל תאחסנו שום דבר אחר על אותו שרת, ובצעו בנייה מחדש של המכונה לפי לוח זמנים.
מדוע הרישום נכשל עם Http response code: NotFound?
קריאת הרישום מחזירה NotFound כאשר פרטי ההזדהות שגויים, ולא רק כאשר ה-URL שגוי, מה שהופך את ההודעה למטעה. אסימוני רישום (registration tokens) פוקעים שעה אחת לאחר הצגתם, ו-personal access token אינו מתקבל עבור קריאה זו. פתחו את Settings, לאחר מכן Actions, Runners, ו-New self-hosted runner שוב, העתיקו את האסימון החדש, וודאו שערך ה---url מצביע על מאגר שבו יש לכם הרשאות ניהול.