11#! /usr/bin/env bash
22set -euo pipefail
33
4+ # 忽略多组织部署时的 orphan 容器警告(同一主机运行多个组织的 runner 是正常场景)
5+ export COMPOSE_IGNORE_ORPHANS=1
6+
47ENV_FILE=" ${ENV_FILE:- .env} "
58
69# ------------------------------- load .env file -------------------------------
1316ORG=" ${ORG:- } "
1417GH_PAT=" ${GH_PAT:- } "
1518REPO=" ${REPO:- } "
16- REG_TOKEN_CACHE_FILE=" ${REG_TOKEN_CACHE_FILE:- .reg_token.cache} "
17- REG_TOKEN_CACHE_TTL=" ${REG_TOKEN_CACHE_TTL:- 300} " # seconds, default 5 minutes
1819
1920# Runner container related parameters
2021RUNNER_IMAGE=" ${RUNNER_IMAGE:- ghcr.io/ actions/ actions-runner: latest} "
@@ -38,15 +39,46 @@ RUNNER_WORKDIR="${RUNNER_WORKDIR:-}"
3839RUNNER_LABELS=" ${RUNNER_LABELS:- intel} "
3940RUNNER_BOARD=" 2"
4041DISABLE_AUTO_UPDATE=" ${DISABLE_AUTO_UPDATE:- false} "
41- # 多组织共享硬件锁:板子 runner 使用 runner-wrapper 时按锁 ID 串行。每块板子「有定义用定义的,否则用该板子默认值」,不再回退到全局 RUNNER_RESOURCE_ID,避免所有板子共一把锁导致无法并行
42- RUNNER_RESOURCE_ID =" ${RUNNER_RESOURCE_ID :- } "
42+ # 普通 runners 的全局资源 ID:可选,设置后普通 runners 会通过 flock 串行执行以避免硬件竞争。多组织部署时推荐设置为相同值(如 "general-runners")
43+ RUNNER_RESOURCE_ID_GENERAL =" ${RUNNER_RESOURCE_ID_GENERAL :- } "
4344# 板子级:未设置时用本板默认值(同类型板串行、不同类型板并行);多组织共享同一块板时显式设为相同 ID 即可
44- RUNNER_RESOURCE_ID_PHYTIUMPI=" ${RUNNER_RESOURCE_ID_PHYTIUMPI:- board-phytiumpi } "
45- RUNNER_RESOURCE_ID_ROC_RK3568_PC=" ${RUNNER_RESOURCE_ID_ROC_RK3568_PC:- board-roc-rk3568-pc } "
45+ RUNNER_RESOURCE_ID_PHYTIUMPI=" ${RUNNER_RESOURCE_ID_PHYTIUMPI:- } "
46+ RUNNER_RESOURCE_ID_ROC_RK3568_PC=" ${RUNNER_RESOURCE_ID_ROC_RK3568_PC:- } "
4647RUNNER_LOCK_DIR=" ${RUNNER_LOCK_DIR:-/ tmp/ github-runner-locks} "
4748RUNNER_LOCK_HOST_PATH=" ${RUNNER_LOCK_HOST_PATH:-/ tmp/ github-runner-locks} "
48- COMPOSE_FILE=" docker-compose.yml"
49- DOCKERFILE_HASH_FILE=" ${DOCKERFILE_HASH_FILE:- .dockerfile.sha256} "
49+ # Compose 文件名:未显式设置时自动拼入 ORG/REPO,避免同一主机多组织时文件冲突
50+ # 组织级默认:docker-compose.<org>.yml 仓库级默认:docker-compose.<org>.<repo>.yml
51+ if [[ -z " ${COMPOSE_FILE:- } " ]]; then
52+ if [[ -n " ${ORG:- } " && -n " ${REPO:- } " ]]; then
53+ COMPOSE_FILE=" docker-compose.${ORG} .${REPO} .yml"
54+ elif [[ -n " ${ORG:- } " ]]; then
55+ COMPOSE_FILE=" docker-compose.${ORG} .yml"
56+ else
57+ COMPOSE_FILE=" docker-compose.yml"
58+ fi
59+ fi
60+ # Dockerfile hash 文件名:同样根据 ORG/REPO 区分,避免多组织时 hash 冲突
61+ if [[ -z " ${DOCKERFILE_HASH_FILE:- } " ]]; then
62+ if [[ -n " ${ORG:- } " && -n " ${REPO:- } " ]]; then
63+ DOCKERFILE_HASH_FILE=" .dockerfile.${ORG} .${REPO} .sha256"
64+ elif [[ -n " ${ORG:- } " ]]; then
65+ DOCKERFILE_HASH_FILE=" .dockerfile.${ORG} .sha256"
66+ else
67+ DOCKERFILE_HASH_FILE=" .dockerfile.sha256"
68+ fi
69+ fi
70+ # REG_TOKEN_CACHE_FILE 文件名:未显式设置时自动拼入 ORG/REPO,避免同一主机多组织时文件冲突
71+ # 组织级默认:.reg_token.cache.<org> 仓库级默认:.reg_token.cache.<org>.<repo>
72+ if [[ -z " ${REG_TOKEN_CACHE_FILE:- } " ]]; then
73+ if [[ -n " ${ORG:- } " && -n " ${REPO:- } " ]]; then
74+ REG_TOKEN_CACHE_FILE=" .reg_token.cache.${ORG} .${REPO} "
75+ elif [[ -n " ${ORG:- } " ]]; then
76+ REG_TOKEN_CACHE_FILE=" .reg_token.cache.${ORG} "
77+ else
78+ REG_TOKEN_CACHE_FILE=" .reg_token.cache"
79+ fi
80+ fi
81+ REG_TOKEN_CACHE_TTL=" ${REG_TOKEN_CACHE_TTL:- 300} " # seconds, default 5 minutes
5082
5183# ------------------------------- Helpers -------------------------------
5284shell_usage () {
@@ -92,7 +124,7 @@ shell_usage() {
92124 printf " %-${KEYW} s %s\n" " RUNNER_NAME_PREFIX" " Container name prefix (default: <hostname>-<org>[-<repo>]-); auto includes ORG/REPO to avoid name conflicts"
93125 printf " %-${KEYW} s %s\n" " RUNNER_IMAGE" " Image used for compose generation (default ghcr.io/actions/actions-runner:latest)"
94126 printf " %-${KEYW} s %s\n" " RUNNER_CUSTOM_IMAGE" " Image tag used for auto-build (can override)"
95- printf " %-${KEYW} s %s\n" " RUNNER_RESOURCE_ID " " Optional global lock ID (e.g. for manual wrapper config ); board vars take per-board default if unset "
127+ printf " %-${KEYW} s %s\n" " RUNNER_RESOURCE_ID_GENERAL " " Lock ID for generic runners (optional ); prevents hardware contention in multi-org deployments "
96128 printf " %-${KEYW} s %s\n" " RUNNER_RESOURCE_ID_PHYTIUMPI" " Lock ID for phytiumpi board (default: board-phytiumpi); same ID = serial across runners"
97129 printf " %-${KEYW} s %s\n" " RUNNER_RESOURCE_ID_ROC_RK3568_PC" " Lock ID for roc-rk3568-pc board (default: board-roc-rk3568-pc); same ID = serial"
98130 printf " %-${KEYW} s %s\n" " RUNNER_LOCK_DIR" " Lock dir in container (default /tmp/github-runner-locks)"
@@ -102,7 +134,7 @@ shell_usage() {
102134
103135 echo
104136 echo " Tips:"
105- echo " - docker-compose.yml must exist. The script will not generate or modify it. "
137+ echo " - Compose file is auto-generated per ORG/REPO (e.g., docker-compose.<org>.yml) "
106138 echo " - Re-start/up will reuse existing volumes; Runner configuration and tool caches will not be lost."
107139}
108140
@@ -288,21 +320,9 @@ shell_get_reg_token() {
288320 local now ts cached_token
289321 now=$( date +%s)
290322
291- # 先确保 ORG 已设置,以便构建正确的缓存文件名
292- shell_get_org_and_pat
293-
294- # 根据组织/仓库构建缓存文件名,支持多组织场景
295- local cache_suffix=" "
296- if [[ -n " ${REPO:- } " ]]; then
297- cache_suffix=" .${ORG} .${REPO} "
298- elif [[ -n " ${ORG:- } " ]]; then
299- cache_suffix=" .${ORG} "
300- fi
301- local token_cache_file=" ${REG_TOKEN_CACHE_FILE}${cache_suffix} "
302-
303- if [[ -f " $token_cache_file " ]]; then
304- ts=$( head -n1 " $token_cache_file " 2> /dev/null || true)
305- cached_token=$( sed -n ' 2p' " $token_cache_file " 2> /dev/null || true)
323+ if [[ -f " $REG_TOKEN_CACHE_FILE " ]]; then
324+ ts=$( head -n1 " $REG_TOKEN_CACHE_FILE " 2> /dev/null || true)
325+ cached_token=$( sed -n ' 2p' " $REG_TOKEN_CACHE_FILE " 2> /dev/null || true)
306326 if [[ -n " $ts " && -n " $cached_token " && " $ts " =~ ^[0-9]+$ ]]; then
307327 if (( now - ts < REG_TOKEN_CACHE_TTL )) ; then
308328 REG_TOKEN=" $cached_token "
@@ -313,18 +333,19 @@ shell_get_reg_token() {
313333 fi
314334
315335 if [[ -n " ${REG_TOKEN:- } " && " ${REG_TOKEN:- } " != " null" ]]; then
316- printf ' %s\n%s\n' " $now " " $REG_TOKEN " > " $token_cache_file "
336+ printf ' %s\n%s\n' " $now " " $REG_TOKEN " > " $REG_TOKEN_CACHE_FILE "
317337 printf ' %s\n' " $REG_TOKEN "
318338 return 0
319339 fi
320340
321- shell_info " Requesting <${ORG}${REPO: +/ }${REPO} > registration token..." >&2
341+ shell_get_org_and_pat
342+ shell_info " Requesting <${ORG:- ${REPO} } > registration token..." >&2
322343 local new_token
323344 new_token=" $( github_fetch_reg_token || true) "
324345 [[ -n " $new_token " && " $new_token " != " null" ]] || shell_die " Failed to fetch registration token!"
325346 REG_TOKEN=" $new_token "
326347 export REG_TOKEN
327- printf ' %s\n%s\n' " $now " " $REG_TOKEN " > " $token_cache_file "
348+ printf ' %s\n%s\n' " $now " " $REG_TOKEN " > " $REG_TOKEN_CACHE_FILE "
328349 # Keep compose file in sync when fetching a fresh token
329350 printf ' %s\n' " $REG_TOKEN "
330351}
@@ -423,21 +444,61 @@ shell_get_compose_file() {
423444
424445shell_generate_compose_file () {
425446 local general_count=$1
426- # 板子级:有定义用定义的,否则用该板默认值;不同板默认不同 ID 可并行,多组织共享同一块板时显式设相同 ID
427- local res_phytiumpi=" ${RUNNER_RESOURCE_ID_PHYTIUMPI:- board-phytiumpi} "
428- local res_roc=" ${RUNNER_RESOURCE_ID_ROC_RK3568_PC:- board-roc-rk3568-pc} "
429- local runner_entrypoint_phytiumpi=" exec /home/runner/run.sh"
430- local runner_entrypoint_roc=" exec /home/runner/run.sh"
431- [[ -n " $res_phytiumpi " ]] && runner_entrypoint_phytiumpi=" exec /home/runner/runner-wrapper/runner-wrapper.sh"
432- [[ -n " $res_roc " ]] && runner_entrypoint_roc=" exec /home/runner/runner-wrapper/runner-wrapper.sh"
447+ # ════════════════════════════════════════════════════════════════
448+ # 第一步:为三种 runner 类型定义资源 ID
449+ # ════════════════════════════════════════════════════════════════
450+ # 硬件板 phytiumpi - 总是启用文件锁
451+ local res_phytiumpi=" ${RUNNER_RESOURCE_ID_PHYTIUMPI:- } "
452+ # 硬件板 roc - 总是启用文件锁
453+ local res_roc=" ${RUNNER_RESOURCE_ID_ROC_RK3568_PC:- } "
454+ # 普通 runners(可选)- 用户可选择启用文件锁,默认不启用(向后兼容)
455+ local res_general=" ${RUNNER_RESOURCE_ID_GENERAL:- } "
456+
457+ # ════════════════════════════════════════════════════════════════
458+ # 第二步:三种 runner 类型的 entrypoint 配置
459+ # ════════════════════════════════════════════════════════════════
460+ # 设计说明:若设置了资源 ID(RUNNER_RESOURCE_ID_*),所有 runner 类型都改用
461+ # runner-wrapper.sh 来管理文件锁——包括普通 runners
462+ # 默认:所有 runner 类型都使用 /home/runner/run.sh(不经过 runner-wrapper)
463+ local runner_entrypoint_phytiumpi=" /home/runner/run.sh"
464+ local runner_entrypoint_roc=" /home/runner/run.sh"
465+ local runner_entrypoint_general=" /home/runner/run.sh"
466+ # 若设置了资源 ID,则改用 runner-wrapper 来处理文件锁(适用于所有 runner 类型)
467+ [[ -n " $res_phytiumpi " ]] && runner_entrypoint_phytiumpi=" /home/runner/runner-wrapper/runner-wrapper.sh"
468+ [[ -n " $res_roc " ]] && runner_entrypoint_roc=" /home/runner/runner-wrapper/runner-wrapper.sh"
469+ [[ -n " $res_general " ]] && runner_entrypoint_general=" /home/runner/runner-wrapper/runner-wrapper.sh"
470+
471+ # ════════════════════════════════════════════════════════════════
472+ # 第三步:为三种 runner 类型准备额外的环境变量数组
473+ # ════════════════════════════════════════════════════════════════
474+ # 重复模式说明:以下三部分几乎完全相同,都是:
475+ # 1. 定义空数组:extra_env_X=()
476+ # 2. 如果有资源 ID,则添加三个环境变量:
477+ # - RUNNER_RESOURCE_ID: 用于锁机制
478+ # - RUNNER_SCRIPT: 给 runner-wrapper 使用的脚本路径
479+ # - RUNNER_LOCK_DIR: 容器内锁文件目录
480+ # 原因:三种 runner 都可能需要文件锁机制,但都是可选的
433481 local extra_env_phytiumpi=()
434482 local extra_env_roc=()
483+ local extra_env_general=()
484+ # 只有设置了相应的资源 ID,才为该类型 runner 添加锁相关环境变量
435485 [[ -n " $res_phytiumpi " ]] && extra_env_phytiumpi=(" RUNNER_RESOURCE_ID: \" $res_phytiumpi \" " " RUNNER_SCRIPT: \" /home/runner/run.sh\" " " RUNNER_LOCK_DIR: \" ${RUNNER_LOCK_DIR:-/ tmp/ github-runner-locks} \" " )
436486 [[ -n " $res_roc " ]] && extra_env_roc=(" RUNNER_RESOURCE_ID: \" $res_roc \" " " RUNNER_SCRIPT: \" /home/runner/run.sh\" " " RUNNER_LOCK_DIR: \" ${RUNNER_LOCK_DIR:-/ tmp/ github-runner-locks} \" " )
487+ [[ -n " $res_general " ]] && extra_env_general=(" RUNNER_RESOURCE_ID: \" $res_general \" " " RUNNER_SCRIPT: \" /home/runner/run.sh\" " " RUNNER_LOCK_DIR: \" ${RUNNER_LOCK_DIR:-/ tmp/ github-runner-locks} \" " )
488+
489+ # ════════════════════════════════════════════════════════════════
490+ # 第四步:为三种 runner 类型准备卷挂载配置
491+ # ════════════════════════════════════════════════════════════════
492+ # 重复模式说明:以下三部分完全相同(除变量名),都实现:
493+ # 如果设置了资源 ID,则挂载主机的锁文件目录到容器内
494+ # 原因:文件锁机制需要在主机和容器间共享锁文件
437495 local extra_vol_phytiumpi=" "
438496 local extra_vol_roc=" "
497+ local extra_vol_general=" "
498+ # 只有设置了相应的资源 ID,才为该类型 runner 挂载锁文件目录
439499 [[ -n " $res_phytiumpi " ]] && extra_vol_phytiumpi=" - ${RUNNER_LOCK_HOST_PATH:-/ tmp/ github-runner-locks} :${RUNNER_LOCK_DIR:-/ tmp/ github-runner-locks} "
440500 [[ -n " $res_roc " ]] && extra_vol_roc=" - ${RUNNER_LOCK_HOST_PATH:-/ tmp/ github-runner-locks} :${RUNNER_LOCK_DIR:-/ tmp/ github-runner-locks} "
501+ [[ -n " $res_general " ]] && extra_vol_general=" - ${RUNNER_LOCK_HOST_PATH:-/ tmp/ github-runner-locks} :${RUNNER_LOCK_DIR:-/ tmp/ github-runner-locks} "
441502
442503 # 使用 printf 输出文件头
443504 printf ' %s\n' \
@@ -463,16 +524,16 @@ shell_generate_compose_file() {
463524 " network_mode: host" \
464525 " privileged: true" \
465526 " " \
466- " services:" > docker-compose.yml
527+ " services:" > " ${COMPOSE_FILE} "
467528
468529 # 生成普通 runners
469- echo " # 普通 runners" >> docker-compose.yml
530+ echo " # 普通 runners" >> ${COMPOSE_FILE}
470531 for i in $( seq 1 $general_count ) ; do
471532 printf ' %s\n' \
472533 " ${RUNNER_NAME_PREFIX} runner-${i} :" \
473534 " <<: *runner_base" \
474535 " container_name: \" ${RUNNER_NAME_PREFIX} runner-${i} \" " \
475- " command: [\" /home/runner/run.sh \" ]" \
536+ " command: [\" ${runner_entrypoint_general} \" ]" \
476537 " devices:" \
477538 " - /dev/loop-control:/dev/loop-control" \
478539 " - /dev/loop0:/dev/loop0" \
@@ -486,16 +547,18 @@ shell_generate_compose_file() {
486547 " <<: *runner_env" \
487548 " RUNNER_NAME: \" ${RUNNER_NAME_PREFIX} runner-${i} \" " \
488549 " RUNNER_LABELS: \" ${RUNNER_LABELS} \" " \
550+ " ${extra_env_general[@]} " \
489551 " volumes:" \
490552 " - ${RUNNER_NAME_PREFIX} runner-${i} -data:/home/runner" \
491553 " - ${RUNNER_NAME_PREFIX} runner-${i} -udev-rules:/etc/udev/rules.d" \
492- " " >> docker-compose.yml
554+ " $extra_vol_general " \
555+ " " >> " ${COMPOSE_FILE} "
493556 done
494557
495558 # 只有当 RUNNER_BOARD 大于 0 时才生成板子 runners
496559 if [[ " ${RUNNER_BOARD} " -gt 0 ]]; then
497560 # 生成板子 runners
498- echo " # 板子专用 runners" >> docker-compose.yml
561+ echo " # 板子专用 runners" >> " ${COMPOSE_FILE} "
499562
500563 # phytiumpi 板子配置
501564 printf ' %s\n' \
@@ -550,7 +613,7 @@ shell_generate_compose_file() {
550613 " $extra_vol_phytiumpi " \
551614 " - ${RUNNER_NAME_PREFIX} runner-phytiumpi-data:/home/runner" \
552615 " - ${RUNNER_NAME_PREFIX} runner-phytiumpi-udev-rules:/etc/udev/rules.d" \
553- " " >> docker-compose.yml
616+ " " >> " ${COMPOSE_FILE} "
554617
555618 # roc-rk3568-pc 板子配置
556619 printf ' %s\n' \
@@ -601,18 +664,18 @@ shell_generate_compose_file() {
601664 " $extra_vol_roc " \
602665 " - ${RUNNER_NAME_PREFIX} runner-roc-rk3568-pc-data:/home/runner" \
603666 " - ${RUNNER_NAME_PREFIX} runner-roc-rk3568-pc-udev-rules:/etc/udev/rules.d" \
604- " " >> docker-compose.yml
667+ " " >> " ${COMPOSE_FILE} "
605668 fi
606669
607670 # 生成 volumes
608- echo " volumes:" >> docker-compose.yml
671+ echo " volumes:" >> ${COMPOSE_FILE}
609672
610673 for i in $( seq 1 $general_count ) ; do
611674 printf ' %s\n' \
612675 " ${RUNNER_NAME_PREFIX} runner-${i} -data:" \
613676 " name: ${RUNNER_NAME_PREFIX} runner-${i} -data" \
614677 " ${RUNNER_NAME_PREFIX} runner-${i} -udev-rules:" \
615- " name: ${RUNNER_NAME_PREFIX} runner-${i} -udev-rules" >> docker-compose.yml
678+ " name: ${RUNNER_NAME_PREFIX} runner-${i} -udev-rules" >> " ${COMPOSE_FILE} "
616679 done
617680
618681 # 只有当 RUNNER_BOARD 大于 0 时才生成板子相关的 volumes
@@ -622,14 +685,14 @@ shell_generate_compose_file() {
622685 " ${RUNNER_NAME_PREFIX} runner-phytiumpi-data:" \
623686 " name: ${RUNNER_NAME_PREFIX} runner-phytiumpi-data" \
624687 " ${RUNNER_NAME_PREFIX} runner-phytiumpi-udev-rules:" \
625- " name: ${RUNNER_NAME_PREFIX} runner-phytiumpi-udev-rules" >> docker-compose.yml
688+ " name: ${RUNNER_NAME_PREFIX} runner-phytiumpi-udev-rules" >> " ${COMPOSE_FILE} "
626689
627690 # 为 roc-rk3568-pc 板子生成 volumes
628691 printf ' %s\n' \
629692 " ${RUNNER_NAME_PREFIX} runner-roc-rk3568-pc-data:" \
630693 " name: ${RUNNER_NAME_PREFIX} runner-roc-rk3568-pc-data" \
631694 " ${RUNNER_NAME_PREFIX} runner-roc-rk3568-pc-udev-rules:" \
632- " name: ${RUNNER_NAME_PREFIX} runner-roc-rk3568-pc-udev-rules" >> docker-compose.yml
695+ " name: ${RUNNER_NAME_PREFIX} runner-roc-rk3568-pc-udev-rules" >> " ${COMPOSE_FILE} "
633696 fi
634697}
635698
@@ -1092,9 +1155,10 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
10921155 shell_delete_all_execute " Confirm unregister of all Runners, delete all containers and volumes, and remove all generated files? [y / N] " || { echo " Operation cancelled!" ; exit 0; }
10931156 fi
10941157 for f in \
1095- " ${REG_TOKEN_CACHE_FILE} " \
1158+ " ${REG_TOKEN_CACHE_FILE} " * \
10961159 " ${DOCKERFILE_HASH_FILE} " \
1097- " $ENV_FILE " ; do
1160+ " $ENV_FILE " \
1161+ " $COMPOSE_FILE " ; do
10981162 if [[ -f " $f " ]]; then
10991163 shell_info " Removing file $f "
11001164 rm -f " $f " || true
0 commit comments