mirror of
https://github.com/coder/coder.git
synced 2026-06-05 22:18:20 +00:00
bddb808b25
Fixes all our Go file imports to match the preferred spec that we've _mostly_ been using. For example: ``` import ( "context" "time" "github.com/prometheus/client_golang/prometheus" "golang.org/x/xerrors" "gopkg.in/natefinch/lumberjack.v2" "cdr.dev/slog/v3" "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/serpent" ) ``` 3 groups: standard library, 3rd partly libs, Coder libs. This PR makes the change across the codebase. The PR in the stack above modifies our formatting to maintain this state of affairs, and is a separate PR so it's possible to review that one in detail.
208 lines
6.6 KiB
Go
208 lines
6.6 KiB
Go
//go:build !slim
|
|
|
|
package cli
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/base64"
|
|
"errors"
|
|
"io"
|
|
"net/url"
|
|
|
|
"golang.org/x/xerrors"
|
|
"tailscale.com/derp"
|
|
"tailscale.com/types/key"
|
|
|
|
agplcoderd "github.com/coder/coder/v2/coderd"
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/cryptorand"
|
|
"github.com/coder/coder/v2/enterprise/aibridged"
|
|
"github.com/coder/coder/v2/enterprise/audit"
|
|
"github.com/coder/coder/v2/enterprise/audit/backends"
|
|
"github.com/coder/coder/v2/enterprise/coderd"
|
|
"github.com/coder/coder/v2/enterprise/coderd/dormancy"
|
|
"github.com/coder/coder/v2/enterprise/coderd/usage"
|
|
"github.com/coder/coder/v2/enterprise/dbcrypt"
|
|
"github.com/coder/coder/v2/enterprise/trialer"
|
|
"github.com/coder/coder/v2/tailnet"
|
|
"github.com/coder/quartz"
|
|
"github.com/coder/serpent"
|
|
)
|
|
|
|
func (r *RootCmd) Server(_ func()) *serpent.Command {
|
|
cmd := r.RootCmd.Server(func(ctx context.Context, options *agplcoderd.Options) (*agplcoderd.API, io.Closer, error) {
|
|
if options.DeploymentValues.DERP.Server.RelayURL.String() != "" {
|
|
_, err := url.Parse(options.DeploymentValues.DERP.Server.RelayURL.String())
|
|
if err != nil {
|
|
return nil, nil, xerrors.Errorf("derp-server-relay-address must be a valid HTTP URL: %w", err)
|
|
}
|
|
}
|
|
|
|
if options.DeploymentValues.DERP.Server.Enable {
|
|
options.DERPServer = derp.NewServer(key.NewNode(), tailnet.Logger(options.Logger.Named("derp")))
|
|
var meshKey string
|
|
err := options.Database.InTx(func(tx database.Store) error {
|
|
// This will block until the lock is acquired, and will be
|
|
// automatically released when the transaction ends.
|
|
err := tx.AcquireLock(ctx, database.LockIDEnterpriseDeploymentSetup)
|
|
if err != nil {
|
|
return xerrors.Errorf("acquire lock: %w", err)
|
|
}
|
|
|
|
meshKey, err = tx.GetDERPMeshKey(ctx)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if !errors.Is(err, sql.ErrNoRows) {
|
|
return xerrors.Errorf("get DERP mesh key: %w", err)
|
|
}
|
|
meshKey, err = cryptorand.String(32)
|
|
if err != nil {
|
|
return xerrors.Errorf("generate DERP mesh key: %w", err)
|
|
}
|
|
err = tx.InsertDERPMeshKey(ctx, meshKey)
|
|
if err != nil {
|
|
return xerrors.Errorf("insert DERP mesh key: %w", err)
|
|
}
|
|
return nil
|
|
}, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if meshKey == "" {
|
|
return nil, nil, xerrors.New("mesh key is empty")
|
|
}
|
|
options.DERPServer.SetMeshKey(meshKey)
|
|
}
|
|
|
|
options.Auditor = audit.NewAuditor(
|
|
options.Database,
|
|
audit.DefaultFilter,
|
|
backends.NewPostgres(options.Database, true),
|
|
backends.NewSlog(options.Logger),
|
|
)
|
|
|
|
options.TrialGenerator = trialer.New(options.Database, "https://v2-licensor.coder.com/trial", coderd.Keys)
|
|
|
|
o := &coderd.Options{
|
|
Options: options,
|
|
AuditLogging: true,
|
|
ConnectionLogging: true,
|
|
BrowserOnly: options.DeploymentValues.BrowserOnly.Value(),
|
|
SCIMAPIKey: []byte(options.DeploymentValues.SCIMAPIKey.Value()),
|
|
RBAC: true,
|
|
DERPServerRelayAddress: options.DeploymentValues.DERP.Server.RelayURL.String(),
|
|
DERPServerRegionID: int(options.DeploymentValues.DERP.Server.RegionID.Value()),
|
|
ProxyHealthInterval: options.DeploymentValues.ProxyHealthStatusInterval.Value(),
|
|
DefaultQuietHoursSchedule: options.DeploymentValues.UserQuietHoursSchedule.DefaultSchedule.Value(),
|
|
ProvisionerDaemonPSK: options.DeploymentValues.Provisioner.DaemonPSK.Value(),
|
|
|
|
CheckInactiveUsersCancelFunc: dormancy.CheckInactiveUsers(ctx, options.Logger, quartz.NewReal(), options.Database, options.Auditor),
|
|
}
|
|
|
|
if encKeys := options.DeploymentValues.ExternalTokenEncryptionKeys.Value(); len(encKeys) != 0 {
|
|
keys := make([][]byte, 0, len(encKeys))
|
|
for idx, ek := range encKeys {
|
|
dk, err := base64.StdEncoding.DecodeString(ek)
|
|
if err != nil {
|
|
return nil, nil, xerrors.Errorf("decode external-token-encryption-key %d: %w", idx, err)
|
|
}
|
|
keys = append(keys, dk)
|
|
}
|
|
cs, err := dbcrypt.NewCiphers(keys...)
|
|
if err != nil {
|
|
return nil, nil, xerrors.Errorf("initialize encryption: %w", err)
|
|
}
|
|
o.ExternalTokenEncryption = cs
|
|
}
|
|
|
|
if o.LicenseKeys == nil {
|
|
o.LicenseKeys = coderd.Keys
|
|
}
|
|
|
|
closers := &multiCloser{}
|
|
|
|
// Create the enterprise API.
|
|
api, err := coderd.New(ctx, o)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
closers.Add(api)
|
|
|
|
// Start the enterprise usage publisher routine. This won't do anything
|
|
// unless the deployment is licensed and one of the licenses has usage
|
|
// publishing enabled.
|
|
publisher := usage.NewTallymanPublisher(ctx, options.Logger, options.Database, o.LicenseKeys,
|
|
usage.PublisherWithHTTPClient(api.HTTPClient),
|
|
)
|
|
err = publisher.Start()
|
|
if err != nil {
|
|
_ = closers.Close()
|
|
return nil, nil, xerrors.Errorf("start usage publisher: %w", err)
|
|
}
|
|
closers.Add(publisher)
|
|
|
|
// In-memory aibridge daemon.
|
|
// TODO(@deansheather): the lifecycle of the aibridged server is
|
|
// probably better managed by the enterprise API type itself. Managing
|
|
// it in the API type means we can avoid starting it up when the license
|
|
// is not entitled to the feature.
|
|
var aibridgeDaemon *aibridged.Server
|
|
if options.DeploymentValues.AI.BridgeConfig.Enabled {
|
|
aibridgeDaemon, err = newAIBridgeDaemon(api)
|
|
if err != nil {
|
|
return nil, nil, xerrors.Errorf("create aibridged: %w", err)
|
|
}
|
|
|
|
api.RegisterInMemoryAIBridgedHTTPHandler(aibridgeDaemon)
|
|
|
|
// When running as an in-memory daemon, the HTTP handler is wired into the
|
|
// coderd API and therefore is subject to its context. Calling Close() on
|
|
// aibridged will NOT affect in-flight requests but those will be closed once
|
|
// the API server is itself shutdown.
|
|
closers.Add(aibridgeDaemon)
|
|
}
|
|
|
|
// In-memory AI Bridge Proxy daemon
|
|
if options.DeploymentValues.AI.BridgeProxyConfig.Enabled.Value() {
|
|
aiBridgeProxyServer, err := newAIBridgeProxyDaemon(api)
|
|
if err != nil {
|
|
_ = closers.Close()
|
|
return nil, nil, xerrors.Errorf("create aibridgeproxyd: %w", err)
|
|
}
|
|
closers.Add(aiBridgeProxyServer)
|
|
|
|
// Register the handler so coderd can serve the proxy endpoints.
|
|
api.RegisterInMemoryAIBridgeProxydHTTPHandler(aiBridgeProxyServer.Handler())
|
|
}
|
|
|
|
return api.AGPL, closers, nil
|
|
})
|
|
|
|
cmd.AddSubcommands(
|
|
r.dbcryptCmd(),
|
|
)
|
|
return cmd
|
|
}
|
|
|
|
type multiCloser struct {
|
|
closers []io.Closer
|
|
}
|
|
|
|
var _ io.Closer = &multiCloser{}
|
|
|
|
func (m *multiCloser) Add(closer io.Closer) {
|
|
m.closers = append(m.closers, closer)
|
|
}
|
|
|
|
func (m *multiCloser) Close() error {
|
|
var errs []error
|
|
for _, closer := range m.closers {
|
|
if err := closer.Close(); err != nil {
|
|
errs = append(errs, xerrors.Errorf("close %T: %w", closer, err))
|
|
}
|
|
}
|
|
return errors.Join(errs...)
|
|
}
|