New creates a new workspace proxy server. This requires a primary coderd instance to be reachable and the correct authorization access token to be provided. If the proxy cannot authenticate with the primary, this will fail.
(ctx context.Context, opts *Options)
| 163 | // instance to be reachable and the correct authorization access token to be |
| 164 | // provided. If the proxy cannot authenticate with the primary, this will fail. |
| 165 | func New(ctx context.Context, opts *Options) (*Server, error) { |
| 166 | if opts.PrometheusRegistry == nil { |
| 167 | opts.PrometheusRegistry = prometheus.NewRegistry() |
| 168 | } |
| 169 | |
| 170 | if err := opts.Validate(); err != nil { |
| 171 | return nil, err |
| 172 | } |
| 173 | |
| 174 | client := wsproxysdk.New(opts.DashboardURL, opts.ProxySessionToken) |
| 175 | |
| 176 | // Use the configured client if provided. |
| 177 | if opts.HTTPClient != nil { |
| 178 | client.SDKClient.HTTPClient = opts.HTTPClient |
| 179 | } |
| 180 | |
| 181 | info, err := client.SDKClient.BuildInfo(ctx) |
| 182 | if err != nil { |
| 183 | return nil, xerrors.Errorf("buildinfo: %w", errors.Join( |
| 184 | xerrors.Errorf("unable to fetch build info from primary coderd. Are you sure %q is a coderd instance?", opts.DashboardURL), |
| 185 | err, |
| 186 | )) |
| 187 | } |
| 188 | if info.WorkspaceProxy { |
| 189 | return nil, xerrors.Errorf("%q is a workspace proxy, not a primary coderd instance", opts.DashboardURL) |
| 190 | } |
| 191 | // We don't want to crash the proxy if the versions don't match because |
| 192 | // it'll enter crash loop backoff (and most patches don't make any backwards |
| 193 | // incompatible changes to the proxy API anyways) |
| 194 | if !buildinfo.VersionsMatch(info.Version, buildinfo.Version()) { |
| 195 | opts.Logger.Warn(ctx, "workspace proxy version doesn't match Minor.Major version of the primary, please keep them in sync", |
| 196 | slog.F("primary_version", info.Version), |
| 197 | slog.F("proxy_version", buildinfo.Version()), |
| 198 | ) |
| 199 | } |
| 200 | |
| 201 | meshTLSConfig, err := replicasync.CreateDERPMeshTLSConfig(opts.AccessURL.Hostname(), opts.TLSCertificates) |
| 202 | if err != nil { |
| 203 | return nil, xerrors.Errorf("create DERP mesh tls config: %w", err) |
| 204 | } |
| 205 | derpServer := derp.NewServer(key.NewNode(), tailnet.Logger(opts.Logger.Named("net.derp"))) |
| 206 | // Publish DERP stats to expvar, available via the pprof |
| 207 | // debug server (--pprof-enable) at /debug/vars. This avoids |
| 208 | // exposing expvar on the public HTTP router. |
| 209 | expDERPOnce.Do(func() { |
| 210 | if expvar.Get("derp") == nil { |
| 211 | expvar.Publish("derp", derpServer.ExpVar()) |
| 212 | } |
| 213 | }) |
| 214 | if opts.PrometheusRegistry != nil { |
| 215 | opts.PrometheusRegistry.MustRegister(derpmetrics.NewDERPExpvarCollector(derpServer)) |
| 216 | } |
| 217 | |
| 218 | ctx, cancel := context.WithCancel(context.Background()) |
| 219 | |
| 220 | encryptionCache, err := cryptokeys.NewEncryptionCache(ctx, |
| 221 | opts.Logger, |
| 222 | &ProxyFetcher{Client: client}, |
no test coverage detected