@@ -53,6 +53,8 @@ import (
53
53
"gopkg.in/yaml.v3"
54
54
"tailscale.com/tailcfg"
55
55
56
+ "github.com/coder/coder/v2/coderd/database/dbauthz"
57
+
56
58
"cdr.dev/slog"
57
59
"cdr.dev/slog/sloggers/sloghuman"
58
60
"github.com/coder/coder/v2/buildinfo"
@@ -73,6 +75,7 @@ import (
73
75
"github.com/coder/coder/v2/coderd/externalauth"
74
76
"github.com/coder/coder/v2/coderd/gitsshkey"
75
77
"github.com/coder/coder/v2/coderd/httpmw"
78
+ "github.com/coder/coder/v2/coderd/notifications"
76
79
"github.com/coder/coder/v2/coderd/oauthpki"
77
80
"github.com/coder/coder/v2/coderd/prometheusmetrics"
78
81
"github.com/coder/coder/v2/coderd/prometheusmetrics/insights"
@@ -660,6 +663,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
660
663
options .OIDCConfig = oc
661
664
}
662
665
666
+ experiments := coderd .ReadExperiments (
667
+ options .Logger ,options .DeploymentValues .Experiments .Value (),
668
+ )
669
+
663
670
// We'll read from this channel in the select below that tracks shutdown. If it remains
664
671
// nil, that case of the select will just never fire, but it's important not to have a
665
672
// "bare" read on this channel.
@@ -969,6 +976,23 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
969
976
options .WorkspaceUsageTracker = tracker
970
977
defer tracker .Close ()
971
978
979
+ // Manage notifications.
980
+ var notificationsManager * notifications.Manager
981
+ if experiments .Enabled (codersdk .ExperimentNotifications ) {
982
+ cfg := options .DeploymentValues .Notifications
983
+ nlog := logger .Named ("notifications-manager" )
984
+ notificationsManager ,err = notifications .NewManager (cfg ,options .Database ,nlog ,templateHelpers (options ))
985
+ if err != nil {
986
+ return xerrors .Errorf ("failed to instantiate notification manager: %w" ,err )
987
+ }
988
+
989
+ // nolint:gocritic // TODO: create own role.
990
+ notificationsManager .Run (dbauthz .AsSystemRestricted (ctx ),int (cfg .WorkerCount .Value ()))
991
+ notifications .RegisterInstance (notificationsManager )
992
+ }else {
993
+ notifications .RegisterInstance (notifications .NewNoopManager ())
994
+ }
995
+
972
996
// Wrap the server in middleware that redirects to the access URL if
973
997
// the request is not to a local IP.
974
998
var handler http.Handler = coderAPI .RootHandler
@@ -1049,10 +1073,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
1049
1073
case <- stopCtx .Done ():
1050
1074
exitErr = stopCtx .Err ()
1051
1075
waitForProvisionerJobs = true
1052
- _ ,_ = io .WriteString (inv .Stdout ,cliui .Bold ("Stop caught, waiting for provisioner jobs to complete and gracefully exiting. Use ctrl+\\ to force quit" ))
1076
+ _ ,_ = io .WriteString (inv .Stdout ,cliui .Bold ("Stop caught, waiting for provisioner jobs to complete and gracefully exiting. Use ctrl+\\ to force quit\n " ))
1053
1077
case <- interruptCtx .Done ():
1054
1078
exitErr = interruptCtx .Err ()
1055
- _ ,_ = io .WriteString (inv .Stdout ,cliui .Bold ("Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit" ))
1079
+ _ ,_ = io .WriteString (inv .Stdout ,cliui .Bold ("Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit\n " ))
1056
1080
case <- tunnelDone :
1057
1081
exitErr = xerrors .New ("dev tunnel closed unexpectedly" )
1058
1082
case <- pubsubWatchdogTimeout :
@@ -1088,6 +1112,21 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
1088
1112
// Cancel any remaining in-flight requests.
1089
1113
shutdownConns ()
1090
1114
1115
+ if notificationsManager != nil {
1116
+ // Stop the notification manager, which will cause any buffered updates to the store to be flushed.
1117
+ // If the Stop() call times out, messages that were sent but not reflected as such in the store will have
1118
+ // their leases expire after a period of time and will be re-queued for sending.
1119
+ // See CODER_NOTIFICATIONS_LEASE_PERIOD.
1120
+ cliui .Info (inv .Stdout ,"Shutting down notifications manager..." + "\n " )
1121
+ err = shutdownWithTimeout (notificationsManager .Stop ,5 * time .Second )
1122
+ if err != nil {
1123
+ cliui .Warnf (inv .Stderr ,"Notifications manager shutdown took longer than 5s, " +
1124
+ "this may result in duplicate notifications being sent: %s\n " ,err )
1125
+ }else {
1126
+ cliui .Info (inv .Stdout ,"Gracefully shut down notifications manager\n " )
1127
+ }
1128
+ }
1129
+
1091
1130
// Shut down provisioners before waiting for WebSockets
1092
1131
// connections to close.
1093
1132
var wg sync.WaitGroup
@@ -1227,6 +1266,15 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
1227
1266
return serverCmd
1228
1267
}
1229
1268
1269
+ // templateHelpers builds a set of functions which can be called in templates.
1270
+ // We build them here to avoid an import cycle by using coderd.Options in notifications.Manager.
1271
+ // We can later use this to inject whitelabel fields when app name / logo URL are overridden.
1272
+ func templateHelpers (options * coderd.Options )map [string ]any {
1273
+ return map [string ]any {
1274
+ "base_url" :func ()string {return options .AccessURL .String () },
1275
+ }
1276
+ }
1277
+
1230
1278
// printDeprecatedOptions loops through all command options, and prints
1231
1279
// a warning for usage of deprecated options.
1232
1280
func PrintDeprecatedOptions () serpent.MiddlewareFunc {