@@ -55,6 +55,11 @@ import (
55
55
56
56
"cdr.dev/slog"
57
57
"cdr.dev/slog/sloggers/sloghuman"
58
+ "github.com/coder/pretty"
59
+ "github.com/coder/retry"
60
+ "github.com/coder/serpent"
61
+ "github.com/coder/wgtunnel/tunnelsdk"
62
+
58
63
"github.com/coder/coder/v2/buildinfo"
59
64
"github.com/coder/coder/v2/cli/clilog"
60
65
"github.com/coder/coder/v2/cli/cliui"
@@ -64,6 +69,7 @@ import (
64
69
"github.com/coder/coder/v2/coderd/autobuild"
65
70
"github.com/coder/coder/v2/coderd/database"
66
71
"github.com/coder/coder/v2/coderd/database/awsiamrds"
72
+ "github.com/coder/coder/v2/coderd/database/dbauthz"
67
73
"github.com/coder/coder/v2/coderd/database/dbmem"
68
74
"github.com/coder/coder/v2/coderd/database/dbmetrics"
69
75
"github.com/coder/coder/v2/coderd/database/dbpurge"
@@ -73,6 +79,7 @@ import (
73
79
"github.com/coder/coder/v2/coderd/externalauth"
74
80
"github.com/coder/coder/v2/coderd/gitsshkey"
75
81
"github.com/coder/coder/v2/coderd/httpmw"
82
+ "github.com/coder/coder/v2/coderd/notifications"
76
83
"github.com/coder/coder/v2/coderd/oauthpki"
77
84
"github.com/coder/coder/v2/coderd/prometheusmetrics"
78
85
"github.com/coder/coder/v2/coderd/prometheusmetrics/insights"
@@ -97,10 +104,6 @@ import (
97
104
"github.com/coder/coder/v2/provisionersdk"
98
105
sdkproto"github.com/coder/coder/v2/provisionersdk/proto"
99
106
"github.com/coder/coder/v2/tailnet"
100
- "github.com/coder/pretty"
101
- "github.com/coder/retry"
102
- "github.com/coder/serpent"
103
- "github.com/coder/wgtunnel/tunnelsdk"
104
107
)
105
108
106
109
func createOIDCConfig (ctx context.Context ,vals * codersdk.DeploymentValues ) (* coderd.OIDCConfig ,error ) {
@@ -592,6 +595,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
592
595
SSHConfigOptions :configSSHOptions ,
593
596
},
594
597
AllowWorkspaceRenames :vals .AllowWorkspaceRenames .Value (),
598
+ NotificationsEnqueuer :notifications .NewNoopEnqueuer (),// Changed further down if notifications enabled.
595
599
}
596
600
if httpServers .TLSConfig != nil {
597
601
options .TLSCertificates = httpServers .TLSConfig .Certificates
@@ -660,6 +664,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
660
664
options .OIDCConfig = oc
661
665
}
662
666
667
+ experiments := coderd .ReadExperiments (
668
+ options .Logger ,options .DeploymentValues .Experiments .Value (),
669
+ )
670
+
663
671
// We'll read from this channel in the select below that tracks shutdown. If it remains
664
672
// nil, that case of the select will just never fire, but it's important not to have a
665
673
// "bare" read on this channel.
@@ -969,6 +977,32 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
969
977
options .WorkspaceUsageTracker = tracker
970
978
defer tracker .Close ()
971
979
980
+ // Manage notifications.
981
+ var (
982
+ notificationsManager * notifications.Manager
983
+ )
984
+ if experiments .Enabled (codersdk .ExperimentNotifications ) {
985
+ cfg := options .DeploymentValues .Notifications
986
+
987
+ // The enqueuer is responsible for enqueueing notifications to the given store.
988
+ enqueuer ,err := notifications .NewStoreEnqueuer (cfg ,options .Database ,templateHelpers (options ),logger .Named ("notifications.enqueuer" ))
989
+ if err != nil {
990
+ return xerrors .Errorf ("failed to instantiate notification store enqueuer: %w" ,err )
991
+ }
992
+ options .NotificationsEnqueuer = enqueuer
993
+
994
+ // The notification manager is responsible for:
995
+ // - creating notifiers and managing their lifecycles (notifiers are responsible for dequeueing/sending notifications)
996
+ // - keeping the store updated with status updates
997
+ notificationsManager ,err = notifications .NewManager (cfg ,options .Database ,logger .Named ("notifications.manager" ))
998
+ if err != nil {
999
+ return xerrors .Errorf ("failed to instantiate notification manager: %w" ,err )
1000
+ }
1001
+
1002
+ // nolint:gocritic // TODO: create own role.
1003
+ notificationsManager .Run (dbauthz .AsSystemRestricted (ctx ))
1004
+ }
1005
+
972
1006
// Wrap the server in middleware that redirects to the access URL if
973
1007
// the request is not to a local IP.
974
1008
var handler http.Handler = coderAPI .RootHandler
@@ -1049,10 +1083,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
1049
1083
case <- stopCtx .Done ():
1050
1084
exitErr = stopCtx .Err ()
1051
1085
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" ))
1086
+ _ ,_ = io .WriteString (inv .Stdout ,cliui .Bold ("Stop caught, waiting for provisioner jobs to complete and gracefully exiting. Use ctrl+\\ to force quit\n " ))
1053
1087
case <- interruptCtx .Done ():
1054
1088
exitErr = interruptCtx .Err ()
1055
- _ ,_ = io .WriteString (inv .Stdout ,cliui .Bold ("Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit" ))
1089
+ _ ,_ = io .WriteString (inv .Stdout ,cliui .Bold ("Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit\n " ))
1056
1090
case <- tunnelDone :
1057
1091
exitErr = xerrors .New ("dev tunnel closed unexpectedly" )
1058
1092
case <- pubsubWatchdogTimeout :
@@ -1088,6 +1122,21 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
1088
1122
// Cancel any remaining in-flight requests.
1089
1123
shutdownConns ()
1090
1124
1125
+ if notificationsManager != nil {
1126
+ // Stop the notification manager, which will cause any buffered updates to the store to be flushed.
1127
+ // If the Stop() call times out, messages that were sent but not reflected as such in the store will have
1128
+ // their leases expire after a period of time and will be re-queued for sending.
1129
+ // See CODER_NOTIFICATIONS_LEASE_PERIOD.
1130
+ cliui .Info (inv .Stdout ,"Shutting down notifications manager..." + "\n " )
1131
+ err = shutdownWithTimeout (notificationsManager .Stop ,5 * time .Second )
1132
+ if err != nil {
1133
+ cliui .Warnf (inv .Stderr ,"Notifications manager shutdown took longer than 5s, " +
1134
+ "this may result in duplicate notifications being sent: %s\n " ,err )
1135
+ }else {
1136
+ cliui .Info (inv .Stdout ,"Gracefully shut down notifications manager\n " )
1137
+ }
1138
+ }
1139
+
1091
1140
// Shut down provisioners before waiting for WebSockets
1092
1141
// connections to close.
1093
1142
var wg sync.WaitGroup
@@ -1227,6 +1276,15 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
1227
1276
return serverCmd
1228
1277
}
1229
1278
1279
+ // templateHelpers builds a set of functions which can be called in templates.
1280
+ // We build them here to avoid an import cycle by using coderd.Options in notifications.Manager.
1281
+ // We can later use this to inject whitelabel fields when app name / logo URL are overridden.
1282
+ func templateHelpers (options * coderd.Options )map [string ]any {
1283
+ return map [string ]any {
1284
+ "base_url" :func ()string {return options .AccessURL .String () },
1285
+ }
1286
+ }
1287
+
1230
1288
// printDeprecatedOptions loops through all command options, and prints
1231
1289
// a warning for usage of deprecated options.
1232
1290
func PrintDeprecatedOptions () serpent.MiddlewareFunc {