11#! /bin/bash
2- # Debug: Log to syslog to see what's happening
3- logger " EC2-GHA: Starting userdata script"
4- echo " EC2-GHA: Starting userdata script" >&2
2+ set -e
3+ # Helper functions
4+ log () { echo " [$$ (date '+%Y-%m-%d %H:%M:%S')] $$ 1" | tee -a /var/log/runner-setup.log; }
5+ log_error () { log " ERROR: $$ 1" >&2 ; }
6+
7+ # Get metadata (IMDSv2 compatible)
8+ get_metadata () {
9+ local path=" $$ 1"
10+ local token=$$ (curl -X PUT -H " X-aws-ec2-metadata-token-ttl-seconds: 300" http://169.254.169.254/latest/api/token 2> /dev/null || true)
11+ if [ -n " $$ token" ]; then
12+ curl -s -H " X-aws-ec2-metadata-token: $$ token" " http://169.254.169.254/latest/meta-data/$$ path" 2> /dev/null || echo " unknown"
13+ else
14+ curl -s " http://169.254.169.254/latest/meta-data/$$ path" 2> /dev/null || echo " unknown"
15+ fi
16+ }
517
6- # Log any errors to syslog before exiting
18+ logger " EC2-GHA: Starting userdata script "
719trap ' logger "EC2-GHA: Script failed at line $$LINENO with exit code $$?"' ERR
820
9- set -e
10-
11- # Set up error handling and failsafe termination
12- # Get instance ID early for error messages (IMDSv2 compatible)
13- IMDS_TOKEN_EARLY=$$ (curl -X PUT -H " X-aws-ec2-metadata-token-ttl-seconds: 300" http://169.254.169.254/latest/api/token 2> /dev/null || true)
14- if [ -n " $$ IMDS_TOKEN_EARLY" ]; then
15- INSTANCE_ID=$$ (curl -s -H " X-aws-ec2-metadata-token: $$ IMDS_TOKEN_EARLY" http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null || echo " unknown" )
16- else
17- INSTANCE_ID=$$ (curl -s http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null || echo " unknown" )
18- fi
21+ INSTANCE_ID=$$ (get_metadata " instance-id" )
1922
2023terminate_instance () {
2124 local reason=" $$ 1"
22- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] FATAL: $$ reason" | tee -a /var/log/runner-setup.log
23- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Terminating instance $$ INSTANCE_ID due to setup failure" | tee -a /var/log/runner-setup.log
25+ log " FATAL: $$ reason"
26+ log " Terminating instance $$ INSTANCE_ID due to setup failure"
2427
2528 # Try to remove runner if it was partially configured
2629 if [ -f " $$ homedir/config.sh" ] && [ -n " $$ {RUNNER_TOKEN:-}" ]; then
@@ -44,17 +47,17 @@ if ! [[ "$$REGISTRATION_TIMEOUT" =~ ^[0-9]+$$ ]]; then
4447fi
4548logger " EC2-GHA: Registration timeout set to $$ REGISTRATION_TIMEOUT seconds"
4649(
47- echo " [ $$ ( date '+%Y-%m-%d %H:%M:%S')] Watchdog: Starting $$ REGISTRATION_TIMEOUT second timeout" >> /var/log/runner-setup.log
50+ log " Watchdog: Starting $$ REGISTRATION_TIMEOUT second timeout"
4851 sleep $$ REGISTRATION_TIMEOUT
4952 if [ ! -f /var/run/github-runner-registered ]; then
50- echo " [ $$ ( date '+%Y-%m-%d %H:%M:%S')] Watchdog: Registration marker not found after timeout" >> /var/log/runner-setup.log
53+ log " Watchdog: Registration marker not found after timeout"
5154 terminate_instance " Runner failed to register within $$ REGISTRATION_TIMEOUT seconds"
5255 else
53- echo " [ $$ ( date '+%Y-%m-%d %H:%M:%S')] Watchdog: Registration marker found, exiting normally" >> /var/log/runner-setup.log
56+ log " Watchdog: Registration marker found, exiting normally"
5457 fi
5558) &
5659REGISTRATION_WATCHDOG_PID=$$ !
57- echo " [ $$ ( date '+%Y-%m-%d %H:%M:%S')] Watchdog PID: $$ REGISTRATION_WATCHDOG_PID"
60+ log " Watchdog PID: $$ REGISTRATION_WATCHDOG_PID"
5861# Save watchdog PID to file so it survives exec redirect
5962echo $$ REGISTRATION_WATCHDOG_PID > /var/run/github-runner-watchdog.pid
6063
@@ -70,7 +73,7 @@ if [ -z "$$homedir" ] || [ "$$homedir" = "AUTO" ]; then
7073 for user in ubuntu ec2-user centos admin debian fedora alpine arch; do
7174 if id " $$ user" & > /dev/null; then
7275 homedir=" /home/$$ user"
73- echo " [ $$ ( date '+%Y-%m-%d %H:%M:%S')] Auto-detected: $$ homedir"
76+ log " Auto-detected: $$ homedir"
7477 break
7578 fi
7679 done
@@ -87,37 +90,37 @@ if [ -z "$$homedir" ] || [ "$$homedir" = "AUTO" ]; then
8790
8891 if [ -z " $$ homedir" ]; then
8992 homedir=" /home/ec2-user" # Ultimate fallback
90- echo " [ $$ ( date '+%Y-%m-%d %H:%M:%S')] Using fallback: $$ homedir"
93+ log " Using fallback: $$ homedir"
9194 else
9295 # Use stat to get the actual owner of the directory
9396 owner=$$ (stat -c " %U" " $$ homedir" 2> /dev/null || stat -f " %Su" " $$ homedir" 2> /dev/null)
94- echo " [ $$ ( date '+%Y-%m-%d %H:%M:%S')] Detected: $$ homedir ($$ owner)"
97+ log " Detected: $$ homedir ($$ owner)"
9598 fi
9699 fi
97100else
98- echo " [ $$ ( date '+%Y-%m-%d %H:%M:%S')] Using: $$ homedir"
101+ log " Using: $$ homedir"
99102fi
100103
101104# Set up maximum lifetime timeout - do this early to ensure cleanup
102105MAX_LIFETIME_MINUTES=$max_instance_lifetime
103- echo " [ $$ ( date '+%Y-%m-%d %H:%M:%S')] Max lifetime: $$ {MAX_LIFETIME_MINUTES}m"
106+ log " Max lifetime: $$ {MAX_LIFETIME_MINUTES}m"
104107nohup bash -c " sleep $$ {MAX_LIFETIME_MINUTES}m && echo '[$$ (date)] Maximum lifetime reached' && shutdown -h now" > /var/log/max-lifetime.log 2>&1 &
105108
106109# Configure CloudWatch Logs if enabled
107110if [ " $cloudwatch_logs_group " != " " ]; then
108- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Installing CloudWatch agent"
111+ log " Installing CloudWatch agent"
109112 # Use a subshell to prevent CloudWatch failures from stopping the entire script
110113 (
111114
112115 # Wait for dpkg lock to be released (up to 2 minutes)
113- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Waiting for dpkg lock to be released..."
116+ log " Waiting for dpkg lock to be released..."
114117 timeout=120
115118 while fuser /var/lib/dpkg/lock-frontend > /dev/null 2>&1 || fuser /var/lib/dpkg/lock > /dev/null 2>&1 ; do
116119 if [ $$ timeout -le 0 ]; then
117- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] WARNING: dpkg lock timeout, proceeding anyway"
120+ log " WARNING: dpkg lock timeout, proceeding anyway"
118121 break
119122 fi
120- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] dpkg is locked, waiting... ($$ timeout seconds remaining)"
123+ log " dpkg is locked, waiting... ($$ timeout seconds remaining)"
121124 sleep 5
122125 timeout=$$ (( timeout - 5 ))
123126 done
@@ -187,13 +190,13 @@ EOF
187190 -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json \
188191 -s
189192
190- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] CloudWatch agent started"
191- ) || echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] WARNING: CloudWatch agent installation failed, continuing without it"
193+ log " CloudWatch agent started"
194+ ) || log " WARNING: CloudWatch agent installation failed, continuing without it"
192195fi
193196
194197# Configure SSH access if public key provided
195198if [ -n " $ssh_pubkey " ]; then
196- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Configuring SSH access"
199+ log " Configuring SSH access"
197200
198201 # Determine the default user based on the home directory owner
199202 DEFAULT_USER=$$ (stat -c " %U" " $$ homedir" 2> /dev/null || echo " root" )
@@ -211,54 +214,39 @@ if [ -n "$ssh_pubkey" ]; then
211214 chown -R " $$ DEFAULT_USER:$$ DEFAULT_USER" " $$ homedir/.ssh"
212215 fi
213216
214- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] SSH key added for user $$ DEFAULT_USER"
217+ log " SSH key added for user $$ DEFAULT_USER"
215218fi
216219
217220# Fetch instance metadata before redirecting output
218- # Support both IMDSv1 and IMDSv2
219- IMDS_TOKEN=$$ (curl -X PUT -H " X-aws-ec2-metadata-token-ttl-seconds: 300" http://169.254.169.254/latest/api/token 2> /dev/null || true)
220- if [ -n " $$ IMDS_TOKEN" ]; then
221- # IMDSv2
222- INSTANCE_TYPE=$$ (curl -s -H " X-aws-ec2-metadata-token: $$ IMDS_TOKEN" http://169.254.169.254/latest/meta-data/instance-type || echo " unknown" )
223- INSTANCE_ID=$$ (curl -s -H " X-aws-ec2-metadata-token: $$ IMDS_TOKEN" http://169.254.169.254/latest/meta-data/instance-id || echo " unknown" )
224- REGION=$$ (curl -s -H " X-aws-ec2-metadata-token: $$ IMDS_TOKEN" http://169.254.169.254/latest/meta-data/placement/region || echo " unknown" )
225- AZ=$$ (curl -s -H " X-aws-ec2-metadata-token: $$ IMDS_TOKEN" http://169.254.169.254/latest/meta-data/placement/availability-zone || echo " unknown" )
226- else
227- # IMDSv1 fallback
228- INSTANCE_TYPE=$$ (curl -s http://169.254.169.254/latest/meta-data/instance-type || echo " unknown" )
229- INSTANCE_ID=$$ (curl -s http://169.254.169.254/latest/meta-data/instance-id || echo " unknown" )
230- REGION=$$ (curl -s http://169.254.169.254/latest/meta-data/placement/region || echo " unknown" )
231- AZ=$$ (curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone || echo " unknown" )
232- fi
221+ INSTANCE_TYPE=$$ (get_metadata " instance-type" )
222+ INSTANCE_ID=$$ (get_metadata " instance-id" )
223+ REGION=$$ (get_metadata " placement/region" )
224+ AZ=$$ (get_metadata " placement/availability-zone" )
233225
234226# Redirect runner setup logs to a file for CloudWatch
235227exec >> /var/log/runner-setup.log 2>&1
236- echo " [ $$ ( date '+%Y-%m-%d %H:%M:%S')] Starting runner setup"
228+ log " Starting runner setup"
237229
238- echo " [$$ (date '+%Y-%m-%d %H:%M:%S')] Instance metadata:"
239- echo " Instance type: $$ {INSTANCE_TYPE}"
240- echo " Instance ID: $$ {INSTANCE_ID}"
241- echo " Region: $$ {REGION}"
242- echo " Availability zone: $$ {AZ}"
230+ log " Instance metadata: Type=$$ {INSTANCE_TYPE} ID=$$ {INSTANCE_ID} Region=$$ {REGION} AZ=$$ {AZ}"
243231
244- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Working directory: $$ homedir"
232+ log " Working directory: $$ homedir"
245233cd " $$ homedir"
246234echo " $script " > pre-runner-script.sh
247- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Running pre-runner script"
235+ log " Running pre-runner script"
248236source pre-runner-script.sh
249237export RUNNER_ALLOW_RUNASROOT=1
250238# Detect architecture and download appropriate runner
251239ARCH=$$ (uname -m)
252240if [ " $$ ARCH" = " aarch64" ] || [ " $$ ARCH" = " arm64" ]; then
253241 # For ARM, replace x64 with arm64 in the URL
254242 RUNNER_URL=$$ (echo " $runner_release " | sed ' s/x64/arm64/g' )
255- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] ARM detected, using: $$ RUNNER_URL"
243+ log " ARM detected, using: $$ RUNNER_URL"
256244else
257245 RUNNER_URL=" $runner_release "
258- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] x64 detected, using: $$ RUNNER_URL"
246+ log " x64 detected, using: $$ RUNNER_URL"
259247fi
260248curl -L $$ RUNNER_URL -o runner.tar.gz
261- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Extracting runner"
249+ log " Extracting runner"
262250# `--no-overwrite-dir` is important, otherwise `$$homedir` ends up `chown`'d to `1001:docker`, and `sshd` will refuse connection attempts to `$$homedir`
263251tar --no-overwrite-dir -xzf runner.tar.gz
264252# Create job tracking scripts
@@ -343,18 +331,9 @@ systemctl daemon-reload
343331systemctl enable runner-termination-check.timer
344332systemctl start runner-termination-check.timer
345333
346- # Get instance metadata for descriptive runner name (IMDSv2 compatible)
347- # Reuse token if available, otherwise get a new one
348- if [ -z " $$ IMDS_TOKEN" ]; then
349- IMDS_TOKEN=$$ (curl -X PUT -H " X-aws-ec2-metadata-token-ttl-seconds: 300" http://169.254.169.254/latest/api/token 2> /dev/null || true)
350- fi
351- if [ -n " $$ IMDS_TOKEN" ]; then
352- INSTANCE_ID=$$ (curl -s -H " X-aws-ec2-metadata-token: $$ IMDS_TOKEN" http://169.254.169.254/latest/meta-data/instance-id || echo " unknown" )
353- INSTANCE_TYPE=$$ (curl -s -H " X-aws-ec2-metadata-token: $$ IMDS_TOKEN" http://169.254.169.254/latest/meta-data/instance-type || echo " unknown" )
354- else
355- INSTANCE_ID=$$ (curl -s http://169.254.169.254/latest/meta-data/instance-id || echo " unknown" )
356- INSTANCE_TYPE=$$ (curl -s http://169.254.169.254/latest/meta-data/instance-type || echo " unknown" )
357- fi
334+ # Get instance metadata for descriptive runner name
335+ INSTANCE_ID=$$ (get_metadata " instance-id" )
336+ INSTANCE_TYPE=$$ (get_metadata " instance-type" )
358337
359338# Create runner name with just the instance ID for uniqueness
360339RUNNER_NAME=" ec2-$$ {INSTANCE_ID}"
382361# The $labels variable already contains user labels and the critical runner-xxx label from Python
383362ALL_LABELS=" $labels $$ {METADATA_LABELS}"
384363
385- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Configuring runner for repo: $repo "
386- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Runner name: $$ {RUNNER_NAME}"
387- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Labels: $$ {ALL_LABELS}"
364+ log " Configuring runner for repo: $repo "
365+ log " Runner name: $$ {RUNNER_NAME}"
366+ log " Labels: $$ {ALL_LABELS}"
388367# Export token for error handler
389368export RUNNER_TOKEN=" $token "
390- # Use --replace to force replacement if a runner with the same name exists
391- # This prevents interactive prompts that would lose our labels
392- ./config.sh --url https://github.com/$repo --token $token --labels " $$ {ALL_LABELS}" --name " $$ {RUNNER_NAME}" --disableupdate --replace
393- echo " [$$ (date '+%Y-%m-%d %H:%M:%S')] Runner registered successfully"
369+
370+ # Attempt to register. If a runner with the same name exists, config.sh will fail
371+ # We use --unattended to prevent interactive prompts
372+ if ! ./config.sh --url https://github.com/$repo --token $token --labels " $$ {ALL_LABELS}" --name " $$ {RUNNER_NAME}" --disableupdate --unattended; then
373+ log_error " Failed to register runner '$$ {RUNNER_NAME}'"
374+ log_error " This usually means a runner with this name already exists"
375+ log_error " If instance ID is 'unknown', metadata fetching likely failed"
376+ terminate_instance " Runner registration failed"
377+ fi
378+ log " Runner registered successfully"
394379
395380# Mark registration as complete and kill the watchdog
396- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Creating registration marker file at /var/run/github-runner-registered "
381+ log " Creating registration marker"
397382touch /var/run/github-runner-registered
398383ls -la /var/run/github-runner-registered
399384# Read watchdog PID from file (survives exec redirect)
400385if [ -f /var/run/github-runner-watchdog.pid ]; then
401386 WATCHDOG_PID=$$ (cat /var/run/github-runner-watchdog.pid)
402387 if kill -0 $$ WATCHDOG_PID 2> /dev/null; then
403388 kill $$ WATCHDOG_PID 2> /dev/null || true
404- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Killed registration watchdog (PID: $$ WATCHDOG_PID)"
389+ log " Killed registration watchdog (PID: $$ WATCHDOG_PID)"
405390 else
406- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Watchdog process $$ WATCHDOG_PID already terminated"
391+ log " Watchdog process $$ WATCHDOG_PID already terminated"
407392 fi
408393 rm -f /var/run/github-runner-watchdog.pid
409394fi
410395
411- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Starting runner"
396+ log " Starting runner"
412397# Create marker file for cleanup service
413398touch /var/run/github-runner-started
414399# Ensure CloudWatch agent can read diagnostic logs
415400# The cwagent user needs to traverse into $homedir to reach _diag
416401# Make $homedir world-executable (but not readable) so cwagent can traverse it
417402chmod o+x $$ homedir
418- echo " [ $$ (date '+%Y-%m-%d %H:%M:%S')] Made $$ homedir traversable for CloudWatch agent"
403+ log " Made $$ homedir traversable for CloudWatch agent"
419404# Create _diag directory if it doesn't exist
420405mkdir -p $$ homedir/_diag
421406# The _diag files are already world-readable by default, just ensure the directory is too
0 commit comments