Go · 5110 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package main
4
5 import (
6 "os"
7 "time"
8
9 "github.com/spf13/cobra"
10 "github.com/spf13/pflag"
11
12 infralog "github.com/tenseleyFlow/shithub/internal/infra/log"
13 runnerpkg "github.com/tenseleyFlow/shithub/internal/runner"
14 runnerapi "github.com/tenseleyFlow/shithub/internal/runner/api"
15 runnerconfig "github.com/tenseleyFlow/shithub/internal/runner/config"
16 "github.com/tenseleyFlow/shithub/internal/runner/engine"
17 "github.com/tenseleyFlow/shithub/internal/runner/workspace"
18 "github.com/tenseleyFlow/shithub/internal/version"
19 )
20
21 var runConfigPath string
22
23 var runCmd = &cobra.Command{
24 Use: "run",
25 Short: "Claim and execute shithub Actions jobs",
26 RunE: func(cmd *cobra.Command, _ []string) error {
27 cfg, err := runnerconfig.Load(runnerconfig.LoadOptions{
28 ConfigPath: runConfigPath,
29 Overrides: flagOverrides(cmd),
30 })
31 if err != nil {
32 return err
33 }
34 logger := infralog.New(infralog.Options{
35 Level: cfg.Log.Level,
36 Format: cfg.Log.Format,
37 Writer: os.Stderr,
38 }).With("component", "runner")
39
40 workspaces := workspace.New(cfg.Runner.WorkspaceRoot)
41 removed, err := workspaces.Sweep(cfg.Runner.WorkspaceTTL, time.Now().UTC())
42 if err != nil {
43 return err
44 }
45 if removed > 0 {
46 logger.InfoContext(cmd.Context(), "swept stale workspaces", "count", removed)
47 }
48
49 client, err := runnerapi.New(runnerapi.Config{
50 BaseURL: cfg.Server.BaseURL,
51 RunnerToken: cfg.Runner.Token,
52 })
53 if err != nil {
54 return err
55 }
56 hostName, err := os.Hostname()
57 if err != nil {
58 hostName = ""
59 }
60 execEngine := engine.NewDocker(engine.DockerConfig{
61 Binary: cfg.Engine.Kind,
62 DefaultImage: cfg.Engine.DefaultImage,
63 Network: cfg.Engine.Network,
64 Memory: cfg.Engine.Memory,
65 CPUs: cfg.Engine.CPUs,
66 SeccompProfile: cfg.Engine.SeccompProfile,
67 User: cfg.Engine.User,
68 PidsLimit: cfg.Engine.PidsLimit,
69 DNSServers: cfg.Engine.DNSServers,
70 Stdout: os.Stdout,
71 Stderr: os.Stderr,
72 Logger: logger,
73 })
74 r := runnerpkg.New(runnerpkg.Options{
75 API: client,
76 Engine: execEngine,
77 Workspaces: workspaces,
78 Logger: logger,
79 Labels: cfg.Runner.Labels,
80 Capacity: cfg.Runner.Capacity,
81 HostName: hostName,
82 Version: version.String(),
83 PollInterval: cfg.Runner.PollInterval,
84 DefaultImage: cfg.Engine.DefaultImage,
85 Clock: func() time.Time { return time.Now().UTC() },
86 })
87 return r.Run(cmd.Context())
88 },
89 }
90
91 func init() {
92 runCmd.Flags().StringVar(&runConfigPath, "config", "", "Path to runner config file")
93 runCmd.Flags().String("server-url", "", "shithub base URL")
94 runCmd.Flags().String("token", "", "Runner registration token")
95 runCmd.Flags().String("labels", "", "Comma-separated runner labels")
96 runCmd.Flags().Int("capacity", 0, "Maximum concurrent jobs this runner advertises")
97 runCmd.Flags().Duration("poll-interval", 0, "Idle heartbeat interval")
98 runCmd.Flags().String("workspace-root", "", "Workspace root directory")
99 runCmd.Flags().Duration("workspace-ttl", 0, "Startup sweep TTL for stale workspaces")
100 runCmd.Flags().String("engine", "", "Execution engine: docker or podman")
101 runCmd.Flags().String("image", "", "Default container image")
102 runCmd.Flags().String("network", "", "Container network")
103 runCmd.Flags().String("memory", "", "Container memory limit")
104 runCmd.Flags().String("cpus", "", "Container CPU limit")
105 runCmd.Flags().String("seccomp-profile", "", "Container seccomp profile path")
106 runCmd.Flags().String("container-user", "", "Default container user")
107 runCmd.Flags().Int("pids-limit", 0, "Container PID limit")
108 runCmd.Flags().String("network-allowlist", "", "Comma-separated host patterns allowed by the runner DNS policy")
109 runCmd.Flags().String("dns-servers", "", "Comma-separated DNS servers passed to step containers")
110 runCmd.Flags().String("log-level", "", "Log level: debug, info, warn, error")
111 runCmd.Flags().String("log-format", "", "Log format: text or json")
112 }
113
114 func flagOverrides(cmd *cobra.Command) map[string]string {
115 keys := map[string]string{
116 "server-url": "server.base_url",
117 "token": "runner.token",
118 "labels": "runner.labels",
119 "capacity": "runner.capacity",
120 "poll-interval": "runner.poll_interval",
121 "workspace-root": "runner.workspace_root",
122 "workspace-ttl": "runner.workspace_ttl",
123 "engine": "engine.kind",
124 "image": "engine.default_image",
125 "network": "engine.network",
126 "memory": "engine.memory",
127 "cpus": "engine.cpus",
128 "seccomp-profile": "engine.seccomp_profile",
129 "container-user": "engine.user",
130 "pids-limit": "engine.pids_limit",
131 "network-allowlist": "runner.network_allowlist",
132 "dns-servers": "engine.dns_servers",
133 "log-level": "log.level",
134 "log-format": "log.format",
135 }
136 out := make(map[string]string)
137 cmd.Flags().Visit(func(f *pflag.Flag) {
138 if key, ok := keys[f.Name]; ok {
139 out[key] = f.Value.String()
140 }
141 })
142 return out
143 }
144