This repository has no description
1package config
2
3import (
4 "context"
5 "fmt"
6 "time"
7
8 "github.com/bluesky-social/indigo/atproto/syntax"
9 "github.com/sethvargo/go-envconfig"
10 "tangled.org/core/xrpc/serviceauth"
11)
12
13type Server struct {
14 ListenAddr string `env:"LISTEN_ADDR, default=0.0.0.0:6555"`
15 DBPath string `env:"DB_PATH, default=spindle.db"`
16 RepoDir string `env:"REPO_DIR, default=repos"`
17 Hostname string `env:"HOSTNAME, required"`
18 JetstreamEndpoint string `env:"JETSTREAM_ENDPOINT, default=wss://jetstream1.us-west.bsky.network/subscribe"`
19 Tap Tap `env:",prefix=TAP_"`
20 PlcUrl string `env:"PLC_URL, default=https://plc.directory"`
21 Dev bool `env:"DEV, default=false"`
22 DevExtraHosts []string `env:"DEV_EXTRA_HOSTS"`
23 Owner string `env:"OWNER, required"`
24 Secrets Secrets `env:",prefix=SECRETS_"`
25 LogDir string `env:"LOG_DIR, default=/var/log/spindle"`
26 QueueSize int `env:"QUEUE_SIZE, default=100"`
27 MaxJobCount int `env:"MAX_JOB_COUNT, default=2"` // max number of pipelines that run at a time
28 DockerSocket string `env:"DOCKER_SOCKET"` // path to a docker socket to expose to workflow containers
29}
30
31type Tap struct {
32 Embed bool `env:"EMBED, default=true"`
33 Url string `env:"URL, default=http://[::1]:2480"`
34 Bind string `env:"BIND, default=[::1]:2480"`
35 DBPath string `env:"DB_PATH, default=tap.db"`
36 RelayUrl string `env:"RELAY_URL, default=https://bsky.network"`
37 AdminPassword string `env:"ADMIN_PASSWORD"`
38}
39
40func (s Server) Did() syntax.DID {
41 return serviceauth.DidWeb(s.Hostname)
42}
43
44type Secrets struct {
45 Provider string `env:"PROVIDER, default=sqlite"`
46 OpenBao OpenBaoConfig `env:",prefix=OPENBAO_"`
47}
48
49type OpenBaoConfig struct {
50 ProxyAddr string `env:"PROXY_ADDR, default=http://127.0.0.1:8200"`
51 Mount string `env:"MOUNT, default=spindle"`
52}
53
54type NixeryPipelines struct {
55 Nixery string `env:"NIXERY, default=nixery.tangled.sh"`
56 WorkflowTimeout string `env:"WORKFLOW_TIMEOUT, default=5m"`
57 MaxJobMemoryMB int64 `env:"MAX_JOB_MEMORY_MB, default=6144"` // per-container memory limit in MiB (default 6 GiB)
58 MaxConcurrentWorkflows int `env:"MAX_CONCURRENT_WORKFLOWS, default=8"` // max number of workflow containers running at once (memory cap)
59}
60
61type ArtifactStoreDisk struct {
62 Dir string `env:"DIR"`
63}
64
65type ArtifactStoreS3 struct {
66 Bucket string `env:"BUCKET"`
67 Region string `env:"REGION, default=us-east-1"`
68}
69
70type ArtifactStores struct {
71 Disk ArtifactStoreDisk `env:",prefix=DISK_"`
72 S3 ArtifactStoreS3 `env:",prefix=S3_"`
73}
74
75type LegacyS3 struct {
76 LogBucket string `env:"LOG_BUCKET"`
77}
78
79type MicroVMPipelines struct {
80 ImageDir string `env:"IMAGE_DIR"`
81 OverlayDir string `env:"OVERLAY_DIR"` // where microVM temporary disks will live
82 DefaultImage string `env:"DEFAULT_IMAGE, default=nixos-x86_64"`
83 AgentPort uint32 `env:"AGENT_PORT, default=10240"`
84 EnableKVM bool `env:"ENABLE_KVM, default=true"`
85 WorkflowTimeout string `env:"WORKFLOW_TIMEOUT, default=5m"`
86
87 MaxTotalMemoryMiB int64 `env:"MAX_TOTAL_MEMORY_MIB, default=0"`
88 MaxTotalVCPUs int64 `env:"MAX_TOTAL_VCPUS, default=0"`
89 MaxTotalDiskMiB int64 `env:"MAX_TOTAL_DISK_MIB, default=0"`
90
91 MaxWorkflowMemoryMiB int64 `env:"MAX_WORKFLOW_MEMORY_MIB, default=0"`
92 MaxWorkflowVCPUs int64 `env:"MAX_WORKFLOW_VCPUS, default=0"`
93 MaxWorkflowDiskMiB int64 `env:"MAX_WORKFLOW_DISK_MIB, default=0"`
94
95 AgingThreshold time.Duration `env:"AGING_THRESHOLD, default=30s"`
96
97 EnableCgroups bool `env:"ENABLE_CGROUPS, default=false"`
98 CgroupParent string `env:"CGROUP_PARENT, default=self"`
99 CgroupPidsMax int64 `env:"CGROUP_PIDS_MAX, default=4096"`
100 CgroupSwapMaxMiB *int64 `env:"CGROUP_SWAP_MAX_MIB"`
101 // memory.min that will get assigned to the supervisor (spindle itself) cgroup
102 CgroupSupervisorMemoryMinMiB int64 `env:"CGROUP_SUPERVISOR_MEMORY_MIN_MIB, default=512"`
103}
104
105type NixCache struct {
106 ReadURLs []string `env:"READ_URLS"`
107 TrustedPublicKeys []string `env:"TRUSTED_PUBLIC_KEYS"`
108 UploadURL string `env:"UPLOAD_URL"`
109}
110
111// governs how spindle places and runs jobs
112type Role string
113
114const (
115 RoleStandalone Role = "standalone"
116 RoleMill Role = "mill"
117 RoleExecutor Role = "executor"
118)
119
120// fields are selectively active depending on the role
121type Mill struct {
122 URL string `env:"URL"` // mill websocket endpoint dialled by the executor
123 SharedSecret string `env:"SHARED_SECRET"` // the executor's token for dialing the mill
124 MaxPending int `env:"MAX_PENDING, default=100"` // mill pending job queue limit
125 ReconnectGrace time.Duration `env:"RECONNECT_GRACE, default=45s"` // reconnect window before leases are failed
126 Seats int `env:"SEATS, default=4"` // executor seats advertised to the mill
127 Labels []string `env:"LABELS"` // executor capability labels
128 ArtifactStore string `env:"ARTIFACT_STORE"` // store shared by mill and its executors
129}
130
131type Config struct {
132 Role Role `env:"SPINDLE_ROLE, default=standalone"`
133 Server Server `env:",prefix=SPINDLE_SERVER_"`
134 NixeryPipelines NixeryPipelines `env:",prefix=SPINDLE_NIXERY_PIPELINES_"`
135 MicroVMPipelines MicroVMPipelines `env:",prefix=SPINDLE_MICROVM_PIPELINES_"`
136 NixCache NixCache `env:",prefix=SPINDLE_NIX_CACHE_"`
137 ArtifactStores ArtifactStores `env:",prefix=SPINDLE_ARTIFACT_STORES_"`
138 LegacyS3 LegacyS3 `env:",prefix=SPINDLE_S3_"`
139 Mill Mill `env:",prefix=SPINDLE_MILL_"`
140}
141
142func (c *Config) validate() error {
143 switch c.Role {
144 case RoleStandalone, RoleMill:
145 if c.Mill.URL != "" {
146 return fmt.Errorf("SPINDLE_MILL_URL is set but SPINDLE_ROLE=%s; only an executor dials a mill", c.Role)
147 }
148 case RoleExecutor:
149 if c.Mill.URL == "" {
150 return fmt.Errorf("SPINDLE_ROLE=executor requires SPINDLE_MILL_URL (the mill to dial)")
151 }
152 if c.Mill.SharedSecret == "" {
153 return fmt.Errorf("SPINDLE_ROLE=executor requires SPINDLE_MILL_SHARED_SECRET (its executor token)")
154 }
155 default:
156 return fmt.Errorf("unknown SPINDLE_ROLE %q (want standalone, mill, or executor)", c.Role)
157 }
158 return nil
159}
160
161func Load(ctx context.Context) (*Config, error) {
162 var cfg Config
163 err := envconfig.Process(ctx, &cfg)
164 if err != nil {
165 return nil, err
166 }
167 if err := cfg.validate(); err != nil {
168 return nil, err
169 }
170
171 return &cfg, nil
172}