mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-04 07:33:47 +00:00
Adds a weekly and on-demand workflow that runs the cross-compiled tests on Windows Server 2025 Core in a [dockur/windows](https://github.com/dockur/windows) QEMU VM, and fixes the Windows bugs it found in host and UNC paths, env handling, shell quoting, post-task scripts, health checks, checkout, action symlinks, step summaries, Git client certificates and docker mode from a Windows host. Host-mode steps now emit UTF-8, `bash` falls back to Git's bash instead of the WSL launcher, the runner can run as a native Windows service, steps can keep background helpers with `RUNNER_TRACKING_ID`, and Windows arm64 binaries are released. Fixes: https://gitea.com/gitea/runner/issues/1339 Fixes: https://gitea.com/gitea/runner/issues/637 Fixes: https://gitea.com/gitea/runner/issues/1172 Fixes: https://gitea.com/gitea/runner/issues/347 Reviewed-on: https://gitea.com/gitea/runner/pulls/1348 Reviewed-by: bircni <bircni@icloud.com>
108 lines
2.7 KiB
Go
108 lines
2.7 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"gitea.com/gitea/runner/act/common/logging"
|
|
|
|
"golang.org/x/sys/windows/svc"
|
|
)
|
|
|
|
func runService(ctx context.Context, run func(context.Context) error) error {
|
|
isService, err := svc.IsWindowsService()
|
|
if err != nil {
|
|
return fmt.Errorf("detect Windows service: %w", err)
|
|
}
|
|
if !isService {
|
|
return run(ctx)
|
|
}
|
|
|
|
handler := &daemonService{ctx: ctx, run: func(ctx context.Context) error {
|
|
executable, err := os.Executable()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.Chdir(filepath.Dir(executable)); err != nil {
|
|
return err
|
|
}
|
|
logFile, err := os.OpenFile("gitea-runner.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600)
|
|
if err != nil {
|
|
return fmt.Errorf("open service log: %w", err)
|
|
}
|
|
defer func() { _ = logFile.Close() }()
|
|
previousOutput := logging.Output()
|
|
logging.SetOutput(logFile)
|
|
defer logging.SetOutput(previousOutput)
|
|
err = run(ctx)
|
|
if errors.Is(err, context.Canceled) && ctx.Err() != nil {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
logging.WithError(err).Error("runner daemon failed")
|
|
}
|
|
return err
|
|
}}
|
|
if err := svc.Run("", handler); err != nil { // SCM supplies the name for an own-process service.
|
|
return fmt.Errorf("run Windows service: %w", err)
|
|
}
|
|
return handler.err
|
|
}
|
|
|
|
type daemonService struct {
|
|
ctx context.Context
|
|
run func(context.Context) error
|
|
err error
|
|
}
|
|
|
|
func (s *daemonService) Execute(_args []string, requests <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
|
|
ticker := time.NewTicker(time.Second)
|
|
defer ticker.Stop()
|
|
return s.execute(requests, changes, ticker.C)
|
|
}
|
|
|
|
func (s *daemonService) execute(requests <-chan svc.ChangeRequest, changes chan<- svc.Status, ticks <-chan time.Time) (bool, uint32) {
|
|
changes <- svc.Status{State: svc.StartPending}
|
|
ctx, cancel := context.WithCancel(s.ctx)
|
|
defer cancel()
|
|
done := make(chan error, 1)
|
|
go func() { done <- s.run(ctx) }()
|
|
status := svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPreShutdown}
|
|
changes <- status
|
|
for {
|
|
select {
|
|
case s.err = <-done:
|
|
if status.State != svc.StopPending {
|
|
changes <- svc.Status{State: svc.StopPending}
|
|
}
|
|
if s.err != nil {
|
|
return true, 1
|
|
}
|
|
return false, 0
|
|
case <-ticks:
|
|
if status.State == svc.StopPending {
|
|
status.CheckPoint++
|
|
changes <- status
|
|
}
|
|
case request := <-requests:
|
|
switch request.Cmd {
|
|
case svc.Interrogate:
|
|
changes <- status
|
|
case svc.Stop, svc.Shutdown, svc.PreShutdown:
|
|
if status.State != svc.StopPending {
|
|
status = svc.Status{State: svc.StopPending, CheckPoint: 1, WaitHint: 10000}
|
|
changes <- status
|
|
cancel()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|