Skip to content

Commit 1147443

Browse files
feat: create runner scripts for macos
1 parent 85befba commit 1147443

4 files changed

Lines changed: 302 additions & 3 deletions

File tree

modules/runners/main.tf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ locals {
2626
default_userdata_template = {
2727
"windows" = "${path.module}/templates/user-data.ps1"
2828
"linux" = "${path.module}/templates/user-data.sh"
29-
"osx" = "${path.module}/templates/user-data.sh"
29+
"osx" = "${path.module}/templates/user-data-osx.sh"
3030
}
3131

3232
userdata_install_runner = {
3333
"windows" = "${path.module}/templates/install-runner.ps1"
3434
"linux" = "${path.module}/templates/install-runner.sh"
35-
"osx" = "${path.module}/templates/install-runner.sh"
35+
"osx" = "${path.module}/templates/install-runner-osx.sh"
3636
}
3737

3838
userdata_start_runner = {
3939
"windows" = "${path.module}/templates/start-runner.ps1"
4040
"linux" = "${path.module}/templates/start-runner.sh"
41-
"osx" = "${path.module}/templates/start-runner.sh"
41+
"osx" = "${path.module}/templates/start-runner-osx.sh"
4242
}
4343

4444
# Handle AMI configuration from either the new object or old variables
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# shellcheck shell=bash
2+
3+
## install the runner (macOS)
4+
5+
s3_location=${S3_LOCATION_RUNNER_DISTRIBUTION}
6+
architecture=${RUNNER_ARCHITECTURE}
7+
8+
if [ -z "$RUNNER_TARBALL_URL" ] && [ -z "$s3_location" ]; then
9+
echo "Neither RUNNER_TARBALL_URL or s3_location are set"
10+
exit 1
11+
fi
12+
13+
file_name="actions-runner.tar.gz"
14+
15+
echo "Setting up GH Actions runner tool cache"
16+
mkdir -p /opt/hostedtoolcache
17+
18+
echo "Creating actions-runner directory for the GH Action installation"
19+
sudo mkdir -p /opt/actions-runner
20+
cd /opt/actions-runner || exit 1
21+
22+
if [[ -n "$RUNNER_TARBALL_URL" ]]; then
23+
echo "Downloading the GH Action runner from $RUNNER_TARBALL_URL to $file_name"
24+
curl -s -o "$file_name" -L "$RUNNER_TARBALL_URL"
25+
else
26+
echo "Retrieving REGION from AWS API"
27+
token="$(curl -s -f -X PUT "http://169.254.169.254/latest/api/token" \
28+
-H "X-aws-ec2-metadata-token-ttl-seconds: 180")"
29+
30+
region="$(curl -s -f -H "X-aws-ec2-metadata-token: $token" \
31+
http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r .region)"
32+
echo "Retrieved REGION from AWS API ($region)"
33+
34+
echo "Downloading the GH Action runner from s3 bucket $s3_location"
35+
aws s3 cp "$s3_location" "$file_name" --region "$region" --no-progress
36+
fi
37+
38+
echo "Un-tar action runner"
39+
tar xzf "./$file_name"
40+
echo "Delete tar file"
41+
rm -rf "$file_name"
42+
43+
os_name=$(sw_vers -productName 2>/dev/null || echo "macOS")
44+
os_version=$(sw_vers -productVersion 2>/dev/null || echo "unknown")
45+
arch_name=$(uname -m)
46+
47+
echo "OS: $os_name $os_version ($arch_name)"
48+
49+
if ! command -v brew >/dev/null 2>&1; then
50+
echo "Homebrew not found; skipping dependency installation via brew"
51+
else
52+
echo "Homebrew detected; install any macOS-specific dependencies here if needed"
53+
# Example: brew install jq awscli
54+
fi
55+
56+
user_name="${RUNNER_USER:-ec2-user}"
57+
58+
echo "Set file ownership of action runner"
59+
sudo chown -R "$user_name":"$user_name" /opt/actions-runner
60+
sudo chown -R "$user_name":"$user_name" /opt/hostedtoolcache
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
# macOS variant of start-runner.sh
6+
7+
tag_instance_with_runner_id() {
8+
echo "Checking for .runner file to extract agent ID"
9+
10+
if [[ ! -f "/opt/actions-runner/.runner" ]]; then
11+
echo "Warning: .runner file not found"
12+
return 0
13+
fi
14+
15+
echo "Found .runner file, extracting agent ID"
16+
local agent_id
17+
agent_id=$(jq -r '.agentId' /opt/actions-runner/.runner 2>/dev/null || echo "")
18+
19+
if [[ -z "$agent_id" || "$agent_id" == "null" ]]; then
20+
echo "Warning: Could not extract agent ID from .runner file"
21+
return 0
22+
fi
23+
24+
echo "Tagging instance with GitHub runner agent ID: $agent_id"
25+
if aws ec2 create-tags \
26+
--region "$region" \
27+
--resources "$instance_id" \
28+
--tags Key=ghr:github_runner_id,Value="$agent_id"; then
29+
echo "Successfully tagged instance with agent ID: $agent_id"
30+
return 0
31+
else
32+
echo "Warning: Failed to tag instance with agent ID"
33+
return 0
34+
fi
35+
}
36+
37+
cleanup() {
38+
local exit_code="$1"
39+
40+
if [ "$exit_code" -ne 0 ]; then
41+
echo "ERROR: runner-start-failed with exit code $exit_code"
42+
fi
43+
44+
if [ "$agent_mode" = "ephemeral" ] || [ "$exit_code" -ne 0 ]; then
45+
echo "Terminating instance"
46+
aws ec2 terminate-instances \
47+
--instance-ids "$instance_id" \
48+
--region "$region" || true
49+
fi
50+
}
51+
52+
trap 'cleanup $?' EXIT
53+
54+
echo "Retrieving TOKEN from AWS API"
55+
token=$(curl -f -X PUT "http://169.254.169.254/latest/api/token" \
56+
-H "X-aws-ec2-metadata-token-ttl-seconds: 180" || true)
57+
if [ -z "$token" ]; then
58+
retrycount=0
59+
until [ -n "$token" ]; do
60+
echo "Failed to retrieve token. Retrying in 5 seconds."
61+
sleep 5
62+
token=$(curl -f -X PUT "http://169.254.169.254/latest/api/token" \
63+
-H "X-aws-ec2-metadata-token-ttl-seconds: 180" || true)
64+
retrycount=$((retrycount + 1))
65+
if [ $retrycount -gt 40 ]; then
66+
break
67+
fi
68+
done
69+
fi
70+
71+
region=$(curl -f -H "X-aws-ec2-metadata-token: $token" \
72+
http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r .region)
73+
echo "Retrieved REGION from AWS API ($region)"
74+
75+
instance_id=$(curl -f -H "X-aws-ec2-metadata-token: $token" \
76+
http://169.254.169.254/latest/meta-data/instance-id)
77+
echo "Retrieved INSTANCE_ID from AWS API ($instance_id)"
78+
79+
availability_zone=$(curl -f -H "X-aws-ec2-metadata-token: $token" \
80+
http://169.254.169.254/latest/meta-data/placement/availability-zone)
81+
82+
environment=$(curl -f -H "X-aws-ec2-metadata-token: $token" \
83+
http://169.254.169.254/latest/meta-data/tags/instance/ghr:environment || echo "")
84+
ssm_config_path=$(curl -f -H "X-aws-ec2-metadata-token: $token" \
85+
http://169.254.169.254/latest/meta-data/tags/instance/ghr:ssm_config_path || echo "")
86+
runner_name_prefix=$(curl -f -H "X-aws-ec2-metadata-token: $token" \
87+
http://169.254.169.254/latest/meta-data/tags/instance/ghr:runner_name_prefix || echo "")
88+
89+
echo "Retrieved ghr:environment tag - ($environment)"
90+
echo "Retrieved ghr:ssm_config_path tag - ($ssm_config_path)"
91+
echo "Retrieved ghr:runner_name_prefix tag - ($runner_name_prefix)"
92+
93+
parameters=$(aws ssm get-parameters-by-path \
94+
--path "$ssm_config_path" \
95+
--region "$region" \
96+
--query "Parameters[*].{Name:Name,Value:Value}")
97+
echo "Retrieved parameters from AWS SSM ($parameters)"
98+
99+
run_as=$(echo "$parameters" | jq -r '.[] | select(.Name == "'$ssm_config_path'/run_as") | .Value')
100+
echo "Retrieved /$ssm_config_path/run_as parameter - ($run_as)"
101+
102+
agent_mode=$(echo "$parameters" | jq -r '.[] | select(.Name == "'$ssm_config_path'/agent_mode") | .Value')
103+
echo "Retrieved /$ssm_config_path/agent_mode parameter - ($agent_mode)"
104+
105+
disable_default_labels=$(echo "$parameters" | jq -r '.[] | select(.Name == "'$ssm_config_path'/disable_default_labels") | .Value')
106+
echo "Retrieved /$ssm_config_path/disable_default_labels parameter - ($disable_default_labels)"
107+
108+
enable_jit_config=$(echo "$parameters" | jq -r '.[] | select(.Name == "'$ssm_config_path'/enable_jit_config") | .Value')
109+
echo "Retrieved /$ssm_config_path/enable_jit_config parameter - ($enable_jit_config)"
110+
111+
token_path=$(echo "$parameters" | jq -r '.[] | select(.Name == "'$ssm_config_path'/token_path") | .Value')
112+
echo "Retrieved /$ssm_config_path/token_path parameter - ($token_path)"
113+
114+
echo "Get GH Runner config from AWS SSM"
115+
config=$(aws ssm get-parameter \
116+
--name "$token_path"/"$instance_id" \
117+
--with-decryption \
118+
--region "$region" | jq -r ".Parameter | .Value")
119+
120+
while [[ -z "$config" ]]; do
121+
echo "Waiting for GH Runner config to become available in AWS SSM"
122+
sleep 1
123+
config=$(aws ssm get-parameter \
124+
--name "$token_path"/"$instance_id" \
125+
--with-decryption \
126+
--region "$region" | jq -r ".Parameter | .Value")
127+
done
128+
129+
echo "Delete GH Runner token from AWS SSM"
130+
aws ssm delete-parameter --name "$token_path"/"$instance_id" --region "$region"
131+
132+
if [ -z "$run_as" ]; then
133+
echo "No user specified, using default ec2-user account"
134+
run_as="ec2-user"
135+
fi
136+
137+
if [[ "$run_as" == "root" ]]; then
138+
echo "run_as is set to root - export RUNNER_ALLOW_RUNASROOT=1"
139+
export RUNNER_ALLOW_RUNASROOT=1
140+
fi
141+
142+
sudo chown -R "$run_as" /opt/actions-runner
143+
144+
info_arch=$(uname -m)
145+
info_os=$(sw_vers -productName 2>/dev/null || echo "macOS")
146+
info_ver=$(sw_vers -productVersion 2>/dev/null || echo "unknown")
147+
148+
tee /opt/actions-runner/.setup_info <<EOL
149+
[
150+
{
151+
"group": "Operating System",
152+
"detail": "Distribution: $info_os $info_ver\nArchitecture: $info_arch"
153+
},
154+
{
155+
"group": "EC2",
156+
"detail": "Instance id: $instance_id\nAvailability zone: $availability_zone"
157+
}
158+
]
159+
EOL
160+
161+
echo "Starting runner as user $run_as"
162+
163+
cd /opt/actions-runner || exit 1
164+
165+
if [[ "$enable_jit_config" == "false" || $agent_mode != "ephemeral" ]]; then
166+
echo "Configure GH Runner as user $run_as"
167+
if [[ "$disable_default_labels" == "true" ]]; then
168+
extra_flags="--no-default-labels"
169+
else
170+
extra_flags=""
171+
fi
172+
173+
sudo --preserve-env=RUNNER_ALLOW_RUNASROOT -u "$run_as" -- ./config.sh \
174+
$extra_flags \
175+
--unattended \
176+
--name "$runner_name_prefix$instance_id" \
177+
--work "_work" $config
178+
179+
tag_instance_with_runner_id
180+
fi
181+
182+
if [[ $agent_mode = "ephemeral" ]]; then
183+
echo "Starting the runner in ephemeral mode"
184+
185+
if [[ "$enable_jit_config" == "true" ]]; then
186+
echo "Starting with JIT config"
187+
sudo --preserve-env=RUNNER_ALLOW_RUNASROOT -u "$run_as" -- ./run.sh --jitconfig $config
188+
else
189+
echo "Starting without JIT config"
190+
sudo --preserve-env=RUNNER_ALLOW_RUNASROOT -u "$run_as" -- ./run.sh
191+
fi
192+
echo "Runner has finished"
193+
else
194+
echo "Starting the runner in persistent mode (foreground)"
195+
sudo --preserve-env=RUNNER_ALLOW_RUNASROOT -u "$run_as" -- ./run.sh
196+
fi
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/bin/bash -e
2+
3+
exec > >(tee /var/log/user-data.log | logger -t user-data -s 2>/dev/console) 2>&1
4+
5+
# macOS user-data for GitHub Actions runners
6+
7+
set +x
8+
9+
%{ if enable_debug_logging }
10+
set -x
11+
%{ endif }
12+
13+
${pre_install}
14+
15+
# On macOS we don't use dnf; assume base image has required tools
16+
# Optionally use brew here if needed
17+
if command -v brew >/dev/null 2>&1; then
18+
echo "Homebrew detected; you can install extra dependencies via brew if needed"
19+
fi
20+
21+
user_name=ec2-user
22+
23+
${install_runner}
24+
25+
${post_install}
26+
27+
# Register runner job hooks
28+
# Ref: https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/running-scripts-before-or-after-a-job
29+
%{ if hook_job_started != "" }
30+
cat > /opt/actions-runner/hook_job_started.sh <<'EOF'
31+
${hook_job_started}
32+
EOF
33+
echo ACTIONS_RUNNER_HOOK_JOB_STARTED=/opt/actions-runner/hook_job_started.sh | tee -a /opt/actions-runner/.env
34+
%{ endif }
35+
36+
%{ if hook_job_completed != "" }
37+
cat > /opt/actions-runner/hook_job_completed.sh <<'EOF'
38+
${hook_job_completed}
39+
EOF
40+
echo ACTIONS_RUNNER_HOOK_JOB_COMPLETED=/opt/actions-runner/hook_job_completed.sh | tee -a /opt/actions-runner/.env
41+
%{ endif }
42+
43+
${start_runner}

0 commit comments

Comments
 (0)