@@ -402,91 +402,115 @@ fi
402402# Process each runner configuration
403403log " Setting up $$ RUNNERS_PER_INSTANCE runner(s)"
404404
405- # Check for Python3 availability
406- if ! command -v python3 > /dev/null 2>&1 ; then
407- log_error " Python3 is required but not found. Cannot configure runners."
408- terminate_instance " Python3 not available on this AMI"
409- fi
405+ # Function to configure a single runner
406+ # Export it so it's available to subprocesses
407+ configure_runner () {
408+ local idx=$$ 1
409+ local token=$$ 2
410+ local labels=$$ 3
411+ local homedir=$$ 4
412+ local repo=$$ 5
413+ local instance_id=$$ 6
414+ local runner_grace_period=$$ 7
415+ local runner_initial_grace_period=$$ 8
416+
417+ log " Configuring runner $$ idx..."
418+
419+ # Create runner directory
420+ local runner_dir=" $$ homedir/runner-$$ idx"
421+ mkdir -p " $$ runner_dir"
422+ cd " $$ runner_dir"
423+
424+ # Extract runner
425+ tar -xzf /tmp/runner.tar.gz
426+
427+ # Save token for deregistration
428+ echo " $$ token" > .runner-token
429+
430+ # Create env file
431+ cat > .env << EOF
432+ ACTIONS_RUNNER_HOOK_JOB_STARTED=/usr/local/bin/job-started-hook.sh
433+ ACTIONS_RUNNER_HOOK_JOB_COMPLETED=/usr/local/bin/job-completed-hook.sh
434+ RUNNER_HOME=$$ runner_dir
435+ RUNNER_INDEX=$$ idx
436+ RUNNER_GRACE_PERIOD=$$ runner_grace_period
437+ RUNNER_INITIAL_GRACE_PERIOD=$$ runner_initial_grace_period
438+ EOF
439+
440+ # Configure runner
441+ local runner_name=" ec2-$$ instance_id-$$ idx"
442+ RUNNER_ALLOW_RUNASROOT=1 ./config.sh \
443+ --url " https://github.com/$$ repo" \
444+ --token " $$ token" \
445+ --labels " $$ labels" \
446+ --name " $$ runner_name" \
447+ --disableupdate \
448+ --unattended 2>&1 | tee /tmp/runner-$$ idx-config.log
449+
450+ if grep -q " Runner successfully added" /tmp/runner-$$ idx-config.log; then
451+ log " Runner $$ idx registered successfully"
452+ else
453+ log_error " Failed to register runner $$ idx"
454+ return 1
455+ fi
456+
457+ # Start runner in background
458+ RUNNER_ALLOW_RUNASROOT=1 nohup ./run.sh > /dev/null 2>&1 &
459+ local pid=$$ !
460+ log " Started runner $$ idx in $$ runner_dir (PID: $$ pid)"
461+
462+ return 0
463+ }
464+ # Export the function so it's available to child processes
465+ export -f configure_runner
466+ export -f log
467+ export -f log_error
468+
469+ # Parse simple delimited format: space-delimited tokens, pipe-delimited labels
470+ IFS=' ' read -ra tokens <<< " $runner_tokens"
471+ IFS=' |' read -ra labels <<< " $runner_labels"
472+
473+ num_runners=$$ {# tokens[@]}
474+ log " Configuring $$ num_runners runner(s) in parallel"
475+
476+ # Start configuration for each runner in background
477+ pids=()
478+ for i in $$ {! tokens[@]}; do
479+ token=$$ {tokens[$$ i]}
480+ label=$$ {labels[$$ i]:-} # Default to empty if no label
481+
482+ if [ -z " $$ token" ]; then
483+ log_error " No token for runner $$ i"
484+ continue
485+ fi
486+
487+ # Start configuration in background
488+ (
489+ configure_runner $$ i " $$ token" " $$ {label}$$ METADATA_LABELS" " $$ homedir" " $repo " " $$ INSTANCE_ID" " $runner_grace_period " " $runner_initial_grace_period "
490+ echo $$ ? > /tmp/runner-$$ i-status
491+ ) &
492+ pids+=($$ ! )
493+
494+ log " Started configuration for runner $$ i (PID: $$ {pids[-1]})"
495+ done
496+
497+ # Wait for all background jobs to complete
498+ log " Waiting for all runner configurations to complete..."
499+ failed=0
500+ for i in $$ {! pids[@]}; do
501+ wait $$ {pids[$$ i]}
502+ if [ -f /tmp/runner-$$ i-status ]; then
503+ status=$$ (cat /tmp/runner-$$ i-status)
504+ rm -f /tmp/runner-$$ i-status
505+ if [ " $$ status" != " 0" ]; then
506+ log_error " Runner $$ i configuration failed"
507+ failed=1
508+ fi
509+ fi
510+ done
410511
411- python3 -c "
412- import json, sys, os, subprocess, traceback, base64
413-
414- # Runner configurations passed from Python (base64 encoded to avoid shell escaping issues)
415- runner_configs_b64 = '$runner_configs_b64 '
416- runner_configs_json = base64.b64decode(runner_configs_b64).decode()
417-
418- try:
419- configs = json.loads(runner_configs_json)
420- except Exception as e:
421- print(f'ERROR: Failed to parse runner configs: {e}', file=sys.stderr)
422- print(f'Input was: {runner_configs_json}', file=sys.stderr)
423- traceback.print_exc()
424- sys.exit(1)
425- homedir = '$$ homedir' # Use shell variable (already resolved from AUTO)
426- repo = '$repo '
427- instance_id = '$$ INSTANCE_ID'
428- metadata_labels = '$$ METADATA_LABELS'
429-
430- print(f'Processing {len(configs)} runner configuration(s)', file=sys.stderr)
431- for config in configs:
432- idx = config['runner_idx']
433- token = config['token']
434- labels = config['labels'] + metadata_labels
435-
436- print(f'Configuring runner {idx}...', file=sys.stderr)
437-
438- # Create runner directory
439- runner_dir = f'{homedir}/runner-{idx}'
440- os.makedirs(runner_dir, exist_ok=True)
441- os.chdir(runner_dir)
442-
443- # Extract runner
444- subprocess.run(['tar', '-xzf', '/tmp/runner.tar.gz'], check=True)
445-
446- # Save token for deregistration
447- with open('.runner-token', 'w') as f:
448- f.write(token)
449-
450- # Create env file
451- with open('.env', 'w') as f:
452- f.write(f'ACTIONS_RUNNER_HOOK_JOB_STARTED=/usr/local/bin/job-started-hook.sh\\ n')
453- f.write(f'ACTIONS_RUNNER_HOOK_JOB_COMPLETED=/usr/local/bin/job-completed-hook.sh\\ n')
454- f.write(f'RUNNER_HOME={runner_dir}\\ n')
455- f.write(f'RUNNER_INDEX={idx}\\ n')
456- f.write(f'RUNNER_GRACE_PERIOD=$runner_grace_period \\ n')
457- f.write(f'RUNNER_INITIAL_GRACE_PERIOD=$runner_initial_grace_period \\ n')
458-
459- # Configure runner
460- runner_name = f'ec2-{instance_id}-{idx}'
461- cmd = ['./config.sh', '--url', f'https://github.com/{repo}', '--token', token,
462- '--labels', labels, '--name', runner_name, '--disableupdate', '--unattended']
463- result = subprocess.run(cmd, env={'RUNNER_ALLOW_RUNASROOT': '1'}, capture_output=True, text=True)
464-
465- # Check if registration succeeded (even if config.sh returns non-zero)
466- if 'Runner successfully added' in result.stdout:
467- print(f'Runner {idx} registered successfully', file=sys.stderr)
468- elif result.returncode != 0:
469- print(f'Failed to register runner {idx}: {result.stderr}', file=sys.stderr)
470- sys.exit(1)
471-
472- # Inherit system environment and add RUNNER_ALLOW_RUNASROOT
473- runner_env = os.environ.copy()
474- runner_env['RUNNER_ALLOW_RUNASROOT'] = '1'
475- # Start runner in background (use full path and handle errors)
476- try:
477- proc = subprocess.Popen([f'{runner_dir}/run.sh'],
478- env=runner_env,
479- cwd=runner_dir,
480- stdout=subprocess.DEVNULL,
481- stderr=subprocess.DEVNULL)
482- print(f'Started runner {idx} in {runner_dir} (PID: {proc.pid})', file=sys.stderr)
483- except Exception as e:
484- print(f'Failed to start runner {idx}: {e}', file=sys.stderr)
485- sys.exit(1)
486- "
487-
488- if [ $$ ? -ne 0 ]; then
489- terminate_instance " Failed to register runners"
512+ if [ $$ failed -ne 0 ]; then
513+ terminate_instance " One or more runners failed to register"
490514fi
491515
492516log " All runners registered and started"
0 commit comments