enhance: test on Windows and fix what it found (#1348)

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 <[email protected]>
This commit is contained in:
silverwind committed 2026-11-03 01:26:10 +00:00
1 parent 6e42621b4a
commit d6f5b68a0d
85 files changed
+2578 -620

No files matched your search

+14 -9
View File
@@ -168,7 +168,7 @@ func (r *Runner) OnIdle(ctx context.Context) {
}
// Bind-workdir mode: reclaim stale per-task workspace dirs (numeric task IDs).
if r.cfg.Container.BindWorkdir {
r.cleanupStaleDirs(ctx, filepath.FromSlash("/"+strings.TrimLeft(r.cfg.Container.WorkdirParent, "/")), isTaskIDDir)
r.cleanupStaleDirs(ctx, containerWorkdirRoot(r.cfg.Container.WorkdirParent), isTaskIDDir)
}
// Host mode: reclaim per-job scratch dirs left behind when HostEnvironment
// cleanup timed out (e.g. a delete stalled by an AV/EDR filter driver). They
@@ -557,12 +557,12 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
actionCloneDepth = 0
}
workdirParent := strings.TrimLeft(r.cfg.Container.WorkdirParent, "/")
workdirParent := containerWorkdirRoot(r.cfg.Container.WorkdirParent)
if r.cfg.Container.BindWorkdir {
// Append the task ID to isolate concurrent jobs from the same repo.
workdirParent = fmt.Sprintf("%s/%d", workdirParent, task.Id)
workdirParent = filepath.Join(workdirParent, strconv.FormatInt(task.Id, 10))
}
workdir := filepath.FromSlash(fmt.Sprintf("/%s/%s", workdirParent, preset.Repository))
workdir := filepath.Join(workdirParent, filepath.FromSlash(preset.Repository))
if runtime.GOOS == "windows" {
if abs, err := filepath.Abs(workdir); err == nil {
workdir = abs
@@ -648,10 +648,8 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
reporter.SetOutputs(job.Outputs)
if r.cfg.Container.BindWorkdir {
// Remove the entire task-specific directory (e.g. /workspace/<task_id>).
taskDir := filepath.FromSlash("/" + workdirParent)
if err := os.RemoveAll(taskDir); err != nil {
logging.Warnf("failed to clean up workspace %s: %v", taskDir, err)
if err := os.RemoveAll(workdirParent); err != nil {
logging.Warnf("failed to clean up workspace %s: %v", workdirParent, err)
}
}
@@ -821,7 +819,7 @@ func (r *Runner) setHealthStatus(ready bool, reason string) {
func checkFreeDisk(cfg *config.Config) (bool, string) {
root := cfg.Host.WorkdirParent
if cfg.Container.BindWorkdir {
root = filepath.FromSlash("/" + strings.TrimLeft(cfg.Container.WorkdirParent, "/"))
root = containerWorkdirRoot(cfg.Container.WorkdirParent)
}
root = nearestExistingPath(root)
available, err := disk.FreeBytes(root)
@@ -835,6 +833,13 @@ func checkFreeDisk(cfg *config.Config) (bool, string) {
return true, "ok"
}
func containerWorkdirRoot(parent string) string {
if filepath.IsAbs(parent) {
return filepath.Clean(parent)
}
return filepath.Join(string(filepath.Separator), parent)
}
func nearestExistingPath(path string) string {
for path != "" {
if _, err := os.Stat(path); err == nil {