@@ -45,6 +45,8 @@ type Runner struct {
4545 stdout io.Writer
4646 stderr io.Writer
4747 log * logrus.Logger
48+ // metadata stores registration info for run-single mode
49+ metadata * mmds.Metadata
4850}
4951
5052// Option is a functional option for configuring the Runner.
@@ -156,6 +158,13 @@ func (r *Runner) Register(ctx context.Context, metadata *mmds.Metadata) error {
156158 return fmt .Errorf ("failed to create config directory: %w" , err )
157159 }
158160
161+ // Remove any existing config to prevent duplicate [[runners]] entries on restart
162+ // gitlab-runner register appends to existing config, which causes issues if
163+ // the service restarts after a failed run attempt
164+ if err := os .Remove (r .configPath ); err != nil && ! os .IsNotExist (err ) {
165+ r .log .Warnf ("Failed to remove existing config file: %v" , err )
166+ }
167+
159168 // Build registration command
160169 // With the new runner authentication tokens (glrt-*), we use --token directly
161170 // The runner was already created via the API, so we just need to register locally
@@ -194,18 +203,21 @@ func (r *Runner) Register(ctx context.Context, metadata *mmds.Metadata) error {
194203 return fmt .Errorf ("registration failed: %w" , err )
195204 }
196205
206+ // Store metadata for run-single mode
207+ r .metadata = metadata
208+
197209 r .log .Info ("Runner registered successfully" )
198210 return nil
199211}
200212
201213// Run starts the gitlab-runner daemon and blocks until it exits.
202214// This should be called after Register.
203- // The daemon runs with --max-builds 1 to exit after completing one job (ephemeral mode) .
215+ // The runner will continuously poll for jobs until stopped or the context is cancelled .
204216func (r * Runner ) Run (ctx context.Context ) error {
205- r .log .Info ("Starting gitlab-runner daemon (ephemeral mode)" )
217+ r .log .Info ("Starting gitlab-runner daemon (continuous mode)" )
206218
207- // Use run-single or run with max-builds=1 for ephemeral behavior
208- // run-single executes exactly one build and then exits
219+ // Use ' run' command which reads from config.toml
220+ // The runner will poll for jobs until the context is cancelled
209221 args := []string {
210222 "run" ,
211223 "--config" , r .configPath ,
@@ -256,16 +268,33 @@ func (r *Runner) Run(ctx context.Context) error {
256268}
257269
258270// RunOnce starts gitlab-runner to run exactly one job and then exit.
259- // This uses the run-single command which is designed for ephemeral runners.
271+ // Uses run-single command which processes one job and exits - true ephemeral behavior.
272+ // This requires metadata to be stored during Register().
260273func (r * Runner ) RunOnce (ctx context.Context ) error {
261- r .log .Info ("Starting gitlab-runner in single-job mode" )
274+ r .log .Info ("Starting gitlab-runner in single-job mode (run-single) " )
262275
263- // run-single is specifically designed for ephemeral runners
264- // It picks up one job, executes it, and exits
276+ if r .metadata == nil {
277+ return fmt .Errorf ("metadata not available - Register() must be called before RunOnce()" )
278+ }
279+
280+ // Build directories
281+ buildsDir := filepath .Join (r .workDir , "builds" )
282+ cacheDir := filepath .Join (r .workDir , "cache" )
283+
284+ // Use 'run-single' command which executes exactly one job and exits
285+ // This provides true ephemeral behavior without relying on external VM termination
265286 args := []string {
266287 "run-single" ,
267- "--config" , r .configPath ,
268- "--wait-timeout" , "0" , // Wait indefinitely for a job
288+ "--url" , r .metadata .GitLabInstanceURL ,
289+ "--token" , r .metadata .RunnerToken ,
290+ "--executor" , r .executor ,
291+ "--builds-dir" , buildsDir ,
292+ "--cache-dir" , cacheDir ,
293+ }
294+
295+ // Add runner name if available
296+ if r .metadata .RunnerName != "" {
297+ args = append (args , "--name" , r .metadata .RunnerName )
269298 }
270299
271300 cmd := exec .CommandContext (ctx , r .gitlabRunnerPath , args ... )
0 commit comments