diff --git a/cli/notifications_test.go b/cli/notifications_test.go
index 9d7ff8a37abc3..9d775c6f5842b 100644
--- a/cli/notifications_test.go
+++ b/cli/notifications_test.go
@@ -20,7 +20,6 @@ func createOpts(t *testing.T) *coderdtest.Options {
t.Helper()
dt := coderdtest.DeploymentValues(t)
- dt.Experiments = []string{string(codersdk.ExperimentNotifications)}
return &coderdtest.Options{
DeploymentValues: dt,
}
diff --git a/cli/server.go b/cli/server.go
index 561c1bac16375..612548372c00a 100644
--- a/cli/server.go
+++ b/cli/server.go
@@ -56,15 +56,16 @@ import (
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
- "github.com/coder/coder/v2/coderd/entitlements"
- "github.com/coder/coder/v2/coderd/notifications/reports"
- "github.com/coder/coder/v2/coderd/runtimeconfig"
"github.com/coder/pretty"
"github.com/coder/quartz"
"github.com/coder/retry"
"github.com/coder/serpent"
"github.com/coder/wgtunnel/tunnelsdk"
+ "github.com/coder/coder/v2/coderd/entitlements"
+ "github.com/coder/coder/v2/coderd/notifications/reports"
+ "github.com/coder/coder/v2/coderd/runtimeconfig"
+
"github.com/coder/coder/v2/buildinfo"
"github.com/coder/coder/v2/cli/clilog"
"github.com/coder/coder/v2/cli/cliui"
@@ -679,10 +680,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
options.OIDCConfig = oc
}
- experiments := coderd.ReadExperiments(
- options.Logger, options.DeploymentValues.Experiments.Value(),
- )
-
// We'll read from this channel in the select below that tracks shutdown. If it remains
// nil, that case of the select will just never fire, but it's important not to have a
// "bare" read on this channel.
@@ -946,6 +943,33 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
return xerrors.Errorf("write config url: %w", err)
}
+ // Manage notifications.
+ cfg := options.DeploymentValues.Notifications
+ metrics := notifications.NewMetrics(options.PrometheusRegistry)
+ helpers := templateHelpers(options)
+
+ // The enqueuer is responsible for enqueueing notifications to the given store.
+ enqueuer, err := notifications.NewStoreEnqueuer(cfg, options.Database, helpers, logger.Named("notifications.enqueuer"), quartz.NewReal())
+ if err != nil {
+ return xerrors.Errorf("failed to instantiate notification store enqueuer: %w", err)
+ }
+ options.NotificationsEnqueuer = enqueuer
+
+ // The notification manager is responsible for:
+ // - creating notifiers and managing their lifecycles (notifiers are responsible for dequeueing/sending notifications)
+ // - keeping the store updated with status updates
+ notificationsManager, err := notifications.NewManager(cfg, options.Database, helpers, metrics, logger.Named("notifications.manager"))
+ if err != nil {
+ return xerrors.Errorf("failed to instantiate notification manager: %w", err)
+ }
+
+ // nolint:gocritic // TODO: create own role.
+ notificationsManager.Run(dbauthz.AsSystemRestricted(ctx))
+
+ // Run report generator to distribute periodic reports.
+ notificationReportGenerator := reports.NewReportGenerator(ctx, logger.Named("notifications.report_generator"), options.Database, options.NotificationsEnqueuer, quartz.NewReal())
+ defer notificationReportGenerator.Close()
+
// Since errCh only has one buffered slot, all routines
// sending on it must be wrapped in a select/default to
// avoid leaving dangling goroutines waiting for the
@@ -1002,38 +1026,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
options.WorkspaceUsageTracker = tracker
defer tracker.Close()
- // Manage notifications.
- var (
- notificationsManager *notifications.Manager
- )
- if experiments.Enabled(codersdk.ExperimentNotifications) {
- cfg := options.DeploymentValues.Notifications
- metrics := notifications.NewMetrics(options.PrometheusRegistry)
- helpers := templateHelpers(options)
-
- // The enqueuer is responsible for enqueueing notifications to the given store.
- enqueuer, err := notifications.NewStoreEnqueuer(cfg, options.Database, helpers, logger.Named("notifications.enqueuer"), quartz.NewReal())
- if err != nil {
- return xerrors.Errorf("failed to instantiate notification store enqueuer: %w", err)
- }
- options.NotificationsEnqueuer = enqueuer
-
- // The notification manager is responsible for:
- // - creating notifiers and managing their lifecycles (notifiers are responsible for dequeueing/sending notifications)
- // - keeping the store updated with status updates
- notificationsManager, err = notifications.NewManager(cfg, options.Database, helpers, metrics, logger.Named("notifications.manager"))
- if err != nil {
- return xerrors.Errorf("failed to instantiate notification manager: %w", err)
- }
-
- // nolint:gocritic // TODO: create own role.
- notificationsManager.Run(dbauthz.AsSystemRestricted(ctx))
-
- // Run report generator to distribute periodic reports.
- notificationReportGenerator := reports.NewReportGenerator(ctx, logger.Named("notifications.report_generator"), options.Database, options.NotificationsEnqueuer, quartz.NewReal())
- defer notificationReportGenerator.Close()
- }
-
// Wrap the server in middleware that redirects to the access URL if
// the request is not to a local IP.
var handler http.Handler = coderAPI.RootHandler
@@ -1153,19 +1145,17 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
// Cancel any remaining in-flight requests.
shutdownConns()
- if notificationsManager != nil {
- // Stop the notification manager, which will cause any buffered updates to the store to be flushed.
- // If the Stop() call times out, messages that were sent but not reflected as such in the store will have
- // their leases expire after a period of time and will be re-queued for sending.
- // See CODER_NOTIFICATIONS_LEASE_PERIOD.
- cliui.Info(inv.Stdout, "Shutting down notifications manager..."+"\n")
- err = shutdownWithTimeout(notificationsManager.Stop, 5*time.Second)
- if err != nil {
- cliui.Warnf(inv.Stderr, "Notifications manager shutdown took longer than 5s, "+
- "this may result in duplicate notifications being sent: %s\n", err)
- } else {
- cliui.Info(inv.Stdout, "Gracefully shut down notifications manager\n")
- }
+ // Stop the notification manager, which will cause any buffered updates to the store to be flushed.
+ // If the Stop() call times out, messages that were sent but not reflected as such in the store will have
+ // their leases expire after a period of time and will be re-queued for sending.
+ // See CODER_NOTIFICATIONS_LEASE_PERIOD.
+ cliui.Info(inv.Stdout, "Shutting down notifications manager..."+"\n")
+ err = shutdownWithTimeout(notificationsManager.Stop, 5*time.Second)
+ if err != nil {
+ cliui.Warnf(inv.Stderr, "Notifications manager shutdown took longer than 5s, "+
+ "this may result in duplicate notifications being sent: %s\n", err)
+ } else {
+ cliui.Info(inv.Stdout, "Gracefully shut down notifications manager\n")
}
// Shut down provisioners before waiting for WebSockets
diff --git a/coderd/coderd.go b/coderd/coderd.go
index 83a780474825b..cbe008a726636 100644
--- a/coderd/coderd.go
+++ b/coderd/coderd.go
@@ -37,11 +37,12 @@ import (
"tailscale.com/util/singleflight"
"cdr.dev/slog"
+ "github.com/coder/quartz"
+ "github.com/coder/serpent"
+
"github.com/coder/coder/v2/coderd/entitlements"
"github.com/coder/coder/v2/coderd/idpsync"
"github.com/coder/coder/v2/coderd/runtimeconfig"
- "github.com/coder/quartz"
- "github.com/coder/serpent"
agentproto "github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/buildinfo"
@@ -1257,10 +1258,7 @@ func New(options *Options) *API {
})
})
r.Route("/notifications", func(r chi.Router) {
- r.Use(
- apiKeyMiddleware,
- httpmw.RequireExperiment(api.Experiments, codersdk.ExperimentNotifications),
- )
+ r.Use(apiKeyMiddleware)
r.Get("/settings", api.notificationsSettings)
r.Put("/settings", api.putNotificationsSettings)
r.Route("/templates", func(r chi.Router) {
diff --git a/coderd/notifications/manager.go b/coderd/notifications/manager.go
index 3c983b2b3ee3d..8b765bbe88c33 100644
--- a/coderd/notifications/manager.go
+++ b/coderd/notifications/manager.go
@@ -54,6 +54,7 @@ type Manager struct {
runOnce sync.Once
stopOnce sync.Once
+ doneOnce sync.Once
stop chan any
done chan any
@@ -153,7 +154,9 @@ func (m *Manager) Run(ctx context.Context) {
// events, creating a notifier, and publishing bulk dispatch result updates to the store.
func (m *Manager) loop(ctx context.Context) error {
defer func() {
- close(m.done)
+ m.doneOnce.Do(func() {
+ close(m.done)
+ })
m.log.Info(context.Background(), "notification manager stopped")
}()
@@ -364,7 +367,9 @@ func (m *Manager) Stop(ctx context.Context) error {
// If the notifier hasn't been started, we don't need to wait for anything.
// This is only really during testing when we want to enqueue messages only but not deliver them.
if m.notifier == nil {
- close(m.done)
+ m.doneOnce.Do(func() {
+ close(m.done)
+ })
} else {
m.notifier.stop()
}
diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go
index 6cc9c9467e9fd..ca1f4f78aad72 100644
--- a/coderd/notifications/notifications_test.go
+++ b/coderd/notifications/notifications_test.go
@@ -1187,7 +1187,6 @@ func createOpts(t *testing.T) *coderdtest.Options {
t.Helper()
dt := coderdtest.DeploymentValues(t)
- dt.Experiments = []string{string(codersdk.ExperimentNotifications)}
return &coderdtest.Options{
DeploymentValues: dt,
}
diff --git a/coderd/notifications/reports/generator.go b/coderd/notifications/reports/generator.go
index 0e5372aa8a894..2424498146c60 100644
--- a/coderd/notifications/reports/generator.go
+++ b/coderd/notifications/reports/generator.go
@@ -49,7 +49,7 @@ func NewReportGenerator(ctx context.Context, logger slog.Logger, db database.Sto
return nil
}
- err = reportFailedWorkspaceBuilds(ctx, logger, db, enqueuer, clk)
+ err = reportFailedWorkspaceBuilds(ctx, logger, tx, enqueuer, clk)
if err != nil {
return xerrors.Errorf("unable to generate reports with failed workspace builds: %w", err)
}
diff --git a/coderd/notifications_test.go b/coderd/notifications_test.go
index 17598cd812f7f..c4f0a551d4914 100644
--- a/coderd/notifications_test.go
+++ b/coderd/notifications_test.go
@@ -20,7 +20,6 @@ func createOpts(t *testing.T) *coderdtest.Options {
t.Helper()
dt := coderdtest.DeploymentValues(t)
- dt.Experiments = []string{string(codersdk.ExperimentNotifications)}
return &coderdtest.Options{
DeploymentValues: dt,
}
diff --git a/codersdk/deployment.go b/codersdk/deployment.go
index da4f3daabea06..ed4d66001d8d6 100644
--- a/codersdk/deployment.go
+++ b/codersdk/deployment.go
@@ -2901,7 +2901,7 @@ const (
// users to opt-in to via --experimental='*'.
// Experiments that are not ready for consumption by all users should
// not be included here and will be essentially hidden.
-var ExperimentsAll = Experiments{ExperimentNotifications}
+var ExperimentsAll = Experiments{}
// Experiments is a list of experiments.
// Multiple experiments may be enabled at the same time.
diff --git a/codersdk/provisionerdaemons.go b/codersdk/provisionerdaemons.go
index 8fa314fd7849d..7ba10539b671c 100644
--- a/codersdk/provisionerdaemons.go
+++ b/codersdk/provisionerdaemons.go
@@ -12,6 +12,8 @@ import (
"github.com/google/uuid"
"github.com/hashicorp/yamux"
+ "golang.org/x/exp/maps"
+ "golang.org/x/exp/slices"
"golang.org/x/xerrors"
"nhooyr.io/websocket"
@@ -278,9 +280,11 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione
type ProvisionerKeyTags map[string]string
func (p ProvisionerKeyTags) String() string {
+ keys := maps.Keys(p)
+ slices.Sort(keys)
tags := []string{}
- for key, value := range p {
- tags = append(tags, fmt.Sprintf("%s=%s", key, value))
+ for _, key := range keys {
+ tags = append(tags, fmt.Sprintf("%s=%s", key, p[key]))
}
return strings.Join(tags, " ")
}
diff --git a/docs/admin/appearance.md b/docs/admin/appearance.md
index edfd144834254..945d56a802fe8 100644
--- a/docs/admin/appearance.md
+++ b/docs/admin/appearance.md
@@ -1,4 +1,4 @@
-# Appearance (enterprise)
+# Appearance (enterprise) (premium)
Customize the look of your Coder deployment to meet your enterprise
requirements.
@@ -93,7 +93,3 @@ For CLI, use,
export CODER_SUPPORT_LINKS='[{"name": "Hello GitHub", "target": "https://github.com/coder/coder", "icon": "bug"}, {"name": "Hello Slack", "target": "https://codercom.slack.com/archives/C014JH42DBJ", "icon": "https://raw.githubusercontent.com/coder/coder/main/site/static/icon/slack.svg"}, {"name": "Hello Discord", "target": "https://discord.gg/coder", "icon": "https://raw.githubusercontent.com/coder/coder/main/site/static/icon/discord.svg"}, {"name": "Hello Foobar", "target": "https://discord.gg/coder", "icon": "/emojis/1f3e1.png"}]'
coder-server
```
-
-## Up next
-
-- [Enterprise](../enterprise.md)
diff --git a/docs/admin/audit-logs.md b/docs/admin/audit-logs.md
index 8872d382fe79b..e0e17f12e1675 100644
--- a/docs/admin/audit-logs.md
+++ b/docs/admin/audit-logs.md
@@ -122,5 +122,5 @@ entry:
## Enabling this feature
-This feature is only available with an enterprise license.
-[Learn more](../enterprise.md)
+This feature is only available with a
+[Premium or Enterprise license](https://coder.com/pricing).
diff --git a/docs/admin/auth.md b/docs/admin/auth.md
index 7036c0e339757..58fa2045577e3 100644
--- a/docs/admin/auth.md
+++ b/docs/admin/auth.md
@@ -1,7 +1,5 @@
# Authentication
-.
-
By default, Coder is accessible via password authentication. Coder does not
recommend using password authentication in production, and recommends using an
authentication provider with properly configured multi-factor authentication
@@ -227,7 +225,7 @@ your Coder deployment:
CODER_DISABLE_PASSWORD_AUTH=true
```
-## SCIM (enterprise)
+## SCIM (enterprise) (premium)
Coder supports user provisioning and deprovisioning via SCIM 2.0 with header
authentication. Upon deactivation, users are
@@ -249,36 +247,50 @@ CODER_TLS_CLIENT_CERT_FILE=/path/to/cert.pem
CODER_TLS_CLIENT_KEY_FILE=/path/to/key.pem
```
-## Group Sync (enterprise)
+## Group Sync (enterprise) (premium)
If your OpenID Connect provider supports group claims, you can configure Coder
-to synchronize groups in your auth provider to groups within Coder.
+to synchronize groups in your auth provider to groups within Coder. To enable
+group sync, ensure that the `groups` claim is being sent by your OpenID
+provider. You might need to request an additional
+[scope](../reference/cli/server.md#--oidc-scopes) or additional configuration on
+the OpenID provider side.
-To enable group sync, ensure that the `groups` claim is set by adding the
-correct scope to request. If group sync is enabled, the user's groups will be
-controlled by the OIDC provider. This means manual group additions/removals will
-be overwritten on the next login.
+If group sync is enabled, the user's groups will be controlled by the OIDC
+provider. This means manual group additions/removals will be overwritten on the
+next user login.
-```env
-# as an environment variable
-CODER_OIDC_SCOPES=openid,profile,email,groups
-```
+There are two ways you can configure group sync:
-```shell
-# as a flag
---oidc-scopes openid,profile,email,groups
+
+
+## Server Flags
+
+First, confirm that your OIDC provider is sending claims by logging in with OIDC
+and visiting the following URL with an `Owner` account:
+
+```text
+https://[coder.example.com]/api/v2/debug/[your-username]/debug-link
```
-With the `groups` scope requested, we also need to map the `groups` claim name.
-Coder recommends using `groups` for the claim name. This step is necessary if
-your **scope's name** is something other than `groups`.
+You should see a field in either `id_token_claims`, `user_info_claims` or both
+followed by a list of the user's OIDC groups in the response. This is the
+[claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) sent by
+the OIDC provider. See
+[Troubleshooting](#troubleshooting-grouproleorganization-sync) to debug this.
-```env
+> Depending on the OIDC provider, this claim may be named differently. Common
+> ones include `groups`, `memberOf`, and `roles`.
+
+Next configure the Coder server to read groups from the claim name with the
+[OIDC group field](../reference/cli/server.md#--oidc-group-field) server flag:
+
+```sh
# as an environment variable
CODER_OIDC_GROUP_FIELD=groups
```
-```shell
+```sh
# as a flag
--oidc-group-field groups
```
@@ -288,14 +300,16 @@ names in Coder and removed from groups that the user no longer belongs to.
For cases when an OIDC provider only returns group IDs ([Azure AD][azure-gids])
or you want to have different group names in Coder than in your OIDC provider,
-you can configure mapping between the two.
+you can configure mapping between the two with the
+[OIDC group mapping](../reference/cli/server.md#--oidc-group-mapping) server
+flag.
-```env
+```sh
# as an environment variable
CODER_OIDC_GROUP_MAPPING='{"myOIDCGroupID": "myCoderGroupName"}'
```
-```shell
+```sh
# as a flag
--oidc-group-mapping '{"myOIDCGroupID": "myCoderGroupName"}'
```
@@ -313,11 +327,103 @@ coder:
From the example above, users that belong to the `myOIDCGroupID` group in your
OIDC provider will be added to the `myCoderGroupName` group in Coder.
-> **Note:** Groups are only updated on login.
-
[azure-gids]:
https://github.com/MicrosoftDocs/azure-docs/issues/59766#issuecomment-664387195
+## Runtime (Organizations)
+
+> Note: You must have a Premium license with Organizations enabled to use this.
+> [Contact your account team](https://coder.com/contact) for more details
+
+For deployments with multiple [organizations](./organizations.md), you must
+configure group sync at the organization level. In future Coder versions, you
+will be able to configure this in the UI. For now, you must use CLI commands.
+
+First confirm you have the [Coder CLI](../install/index.md) installed and are
+logged in with a user who is an Owner or Organization Admin role. Next, confirm
+that your OIDC provider is sending a groups claim by logging in with OIDC and
+visiting the following URL:
+
+```text
+https://[coder.example.com]/api/v2/debug/[your-username]/debug-link
+```
+
+You should see a field in either `id_token_claims`, `user_info_claims` or both
+followed by a list of the user's OIDC groups in the response. This is the
+[claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) sent by
+the OIDC provider. See
+[Troubleshooting](#troubleshooting-grouproleorganization-sync) to debug this.
+
+> Depending on the OIDC provider, this claim may be named differently. Common
+> ones include `groups`, `memberOf`, and `roles`.
+
+To fetch the current group sync settings for an organization, run the following:
+
+```sh
+coder organizations settings show group-sync \
+ --org \
+ > group-sync.json
+```
+
+The default for an organization looks like this:
+
+```json
+{
+ "field": "",
+ "mapping": null,
+ "regex_filter": null,
+ "auto_create_missing_groups": false
+}
+```
+
+Below is an example that uses the `groups` claim and maps all groups prefixed by
+`coder-` into Coder:
+
+```json
+{
+ "field": "groups",
+ "mapping": null,
+ "regex_filter": "^coder-.*$",
+ "auto_create_missing_groups": true
+}
+```
+
+> Note: You much specify Coder group IDs instead of group names. The fastest way
+> to find the ID for a corresponding group is by visiting
+> `https://coder.example.com/api/v2/groups`.
+
+Here is another example which maps `coder-admins` from the identity provider to
+2 groups in Coder and `coder-users` from the identity provider to another group:
+
+```json
+{
+ "field": "groups",
+ "mapping": {
+ "coder-admins": [
+ "2ba2a4ff-ddfb-4493-b7cd-1aec2fa4c830",
+ "93371154-150f-4b12-b5f0-261bb1326bb4"
+ ],
+ "coder-users": ["2f4bde93-0179-4815-ba50-b757fb3d43dd"]
+ },
+ "regex_filter": null,
+ "auto_create_missing_groups": false
+}
+```
+
+To set these group sync settings, use the following command:
+
+```sh
+coder organizations settings set group-sync \
+ --org \
+ < group-sync.json
+```
+
+Visit the Coder UI to confirm these changes:
+
+
+
+
+
### Group allowlist
You can limit which groups from your identity provider can log in to Coder with
@@ -326,11 +432,36 @@ Users who are not in a matching group will see the following error:

-## Role sync (enterprise)
+## Role sync (enterprise) (premium)
If your OpenID Connect provider supports roles claims, you can configure Coder
-to synchronize roles in your auth provider to deployment-wide roles within
-Coder.
+to synchronize roles in your auth provider to roles within Coder.
+
+There are 2 ways to do role sync. Server Flags assign site wide roles, and
+runtime org role sync assigns organization roles
+
+
+
+## Server Flags
+
+First, confirm that your OIDC provider is sending a roles claim by logging in
+with OIDC and visiting the following URL with an `Owner` account:
+
+```text
+https://[coder.example.com]/api/v2/debug/[your-username]/debug-link
+```
+
+You should see a field in either `id_token_claims`, `user_info_claims` or both
+followed by a list of the user's OIDC roles in the response. This is the
+[claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) sent by
+the OIDC provider. See
+[Troubleshooting](#troubleshooting-grouproleorganization-sync) to debug this.
+
+> Depending on the OIDC provider, this claim may be named differently.
+
+Next configure the Coder server to read groups from the claim name with the
+[OIDC role field](../reference/cli/server.md#--oidc-user-role-field) server
+flag:
Set the following in your Coder server [configuration](./configure.md).
@@ -346,7 +477,136 @@ CODER_OIDC_USER_ROLE_MAPPING='{"TemplateAuthor":["template-admin","user-admin"]}
> One role from your identity provider can be mapped to many roles in Coder
> (e.g. the example above maps to 2 roles in Coder.)
-## Troubleshooting group/role sync
+## Runtime (Organizations)
+
+> Note: You must have a Premium license with Organizations enabled to use this.
+> [Contact your account team](https://coder.com/contact) for more details
+
+For deployments with multiple [organizations](./organizations.md), you can
+configure role sync at the organization level. In future Coder versions, you
+will be able to configure this in the UI. For now, you must use CLI commands.
+
+First, confirm that your OIDC provider is sending a roles claim by logging in
+with OIDC and visiting the following URL with an `Owner` account:
+
+```text
+https://[coder.example.com]/api/v2/debug/[your-username]/debug-link
+```
+
+You should see a field in either `id_token_claims`, `user_info_claims` or both
+followed by a list of the user's OIDC roles in the response. This is the
+[claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) sent by
+the OIDC provider. See
+[Troubleshooting](#troubleshooting-grouproleorganization-sync) to debug this.
+
+> Depending on the OIDC provider, this claim may be named differently.
+
+To fetch the current group sync settings for an organization, run the following:
+
+```sh
+coder organizations settings show role-sync \
+ --org \
+ > role-sync.json
+```
+
+The default for an organization looks like this:
+
+```json
+{
+ "field": "",
+ "mapping": null
+}
+```
+
+Below is an example that uses the `roles` claim and maps `coder-admins` from the
+IDP as an `Organization Admin` and also maps to a custom `provisioner-admin`
+role.
+
+```json
+{
+ "field": "roles",
+ "mapping": {
+ "coder-admins": ["organization-admin"],
+ "infra-admins": ["provisioner-admin"]
+ }
+}
+```
+
+> Note: Be sure to use the `name` field for each role, not the display name. Use
+> `coder organization roles show --org=` to see roles for your
+> organization.
+
+To set these role sync settings, use the following command:
+
+```sh
+coder organizations settings set role-sync \
+ --org \
+ < role-sync.json
+```
+
+Visit the Coder UI to confirm these changes:
+
+
+
+
+
+## Organization Sync (Premium)
+
+> Note: In a future Coder release, this can be managed via the Coder UI instead
+> of server flags.
+
+If your OpenID Connect provider supports groups/role claims, you can configure
+Coder to synchronize claims in your auth provider to organizations within Coder.
+
+First, confirm that your OIDC provider is sending clainms by logging in with
+OIDC and visiting the following URL with an `Owner` account:
+
+```text
+https://[coder.example.com]/api/v2/debug/[your-username]/debug-link
+```
+
+You should see a field in either `id_token_claims`, `user_info_claims` or both
+followed by a list of the user's OIDC groups in the response. This is the
+[claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) sent by
+the OIDC provider. See
+[Troubleshooting](#troubleshooting-grouproleorganization-sync) to debug this.
+
+> Depending on the OIDC provider, this claim may be named differently. Common
+> ones include `groups`, `memberOf`, and `roles`.
+
+Next configure the Coder server to read groups from the claim name with the
+[OIDC organization field](../reference/cli/server.md#--oidc-organization-field)
+server flag:
+
+```sh
+# as an environment variable
+CODER_OIDC_ORGANIZATION_FIELD=groups
+```
+
+Next, fetch the corresponding organization IDs using the following endpoint:
+
+```text
+https://[coder.example.com]/api/v2/organizations
+```
+
+Set the following in your Coder server [configuration](./configure.md).
+
+```env
+CODER_OIDC_ORGANIZATION_MAPPING='{"data-scientists":["d8d9daef-e273-49ff-a832-11fe2b2d4ab1", "70be0908-61b5-4fb5-aba4-4dfb3a6c5787"]}'
+```
+
+> One claim value from your identity provider can be mapped to many
+> organizations in Coder (e.g. the example above maps to 2 organizations in
+> Coder.)
+
+By default, all users are assigned to the default (first) organization. You can
+disable that with:
+
+```env
+CODER_OIDC_ORGANIZATION_ASSIGN_DEFAULT=false
+```
+
+## Troubleshooting group/role/organization sync
Some common issues when enabling group/role sync.
diff --git a/docs/admin/external-auth.md b/docs/admin/external-auth.md
index 4ff0cafc403a0..049b7a80d64d5 100644
--- a/docs/admin/external-auth.md
+++ b/docs/admin/external-auth.md
@@ -195,10 +195,10 @@ Optionally, you can request custom scopes:
CODER_EXTERNAL_AUTH_0_SCOPES="repo:read repo:write write:gpg_key"
```
-### Multiple External Providers (enterprise)
+### Multiple External Providers (enterprise) (premium)
-Multiple providers are an Enterprise feature. [Learn more](../enterprise.md).
-Below is an example configuration with multiple providers.
+Multiple providers are an [Enterprise feature](https://coder.com/pricing). Below
+is an example configuration with multiple providers.
```env
# Provider 1) github.com
diff --git a/docs/admin/groups.md b/docs/admin/groups.md
index b982e7d311448..15a5e1b492e42 100644
--- a/docs/admin/groups.md
+++ b/docs/admin/groups.md
@@ -9,5 +9,5 @@ access to specific templates. They can be defined via the Coder web UI,
## Enabling this feature
-This feature is only available with an enterprise license.
-[Learn more](../enterprise.md)
+This feature is only available with a
+[Premium or Enterprise license](https://coder.com/pricing).
diff --git a/docs/admin/high-availability.md b/docs/admin/high-availability.md
index 8393b1ac186de..8534357d28801 100644
--- a/docs/admin/high-availability.md
+++ b/docs/admin/high-availability.md
@@ -73,4 +73,3 @@ Then, increase the number of pods.
- [Networking](../networking/index.md)
- [Kubernetes](../install/kubernetes.md)
-- [Enterprise](../enterprise.md)
diff --git a/docs/admin/notifications.md b/docs/admin/notifications.md
index d4297fac455d8..91b0b43e42358 100644
--- a/docs/admin/notifications.md
+++ b/docs/admin/notifications.md
@@ -231,7 +231,7 @@ notification is indicated on the right hand side of this table.

-## Delivery Preferences (enterprise)
+## Delivery Preferences (enterprise) (premium)
Administrators can configure which delivery methods are used for each different
[event type](#event-types).
diff --git a/docs/admin/organizations.md b/docs/admin/organizations.md
new file mode 100644
index 0000000000000..9f19faab615e8
--- /dev/null
+++ b/docs/admin/organizations.md
@@ -0,0 +1,110 @@
+# Organizations (Premium)
+
+> Note: Organizations requires a [Premium license](../licensing.md). For more
+> details, [contact your account team](https://coder.com/contact).
+
+Organizations can be used to segment and isolate resources inside a Coder
+deployment for different user groups or projects.
+
+## Example
+
+Here is an example of how one could use organizations to run a Coder deployment
+with multiple platform teams, all with unique resources:
+
+
+
+## The default organization
+
+All Coder deployments start with one organization called `Coder`.
+
+To edit the organization details, navigate to `Deployment -> Organizations` in
+the top bar:
+
+
+
+From there, you can manage the name, icon, description, users, and groups:
+
+
+
+## Additional organizations
+
+Any additional organizations have unique admins, users, templates, provisioners,
+groups, and workspaces. Each organization must have at least one
+[provisioner](./provisioners.md) as the built-in provisioner only applies to the
+default organization.
+
+You can configure [organization/role/group sync](./auth.md) from your identity
+provider to avoid manually assigning users to organizations.
+
+## Creating an organization
+
+### Prerequisites
+
+- Coder v2.16+ deployment with Premium license with Organizations enabled
+ ([contact your account team](https://coder.com/contact)) for more details.
+- User with `Owner` role
+
+### 1. Create the organization
+
+Within the sidebar, click `New organization` to create an organization. In this
+example, we'll create the `data-platform` org.
+
+
+
+From there, let's deploy a provisioner and template for this organization.
+
+### 2. Deploy a provisioner
+
+[Provisioners](../admin/provisioners.md) are organization-scoped and are
+responsible for executing Terraform/OpenTofu to provision the infrastructure for
+workspaces and testing templates. Before creating templates, we must deploy at
+least one provisioner as the built-in provisioners are scoped to the default
+organization.
+
+Using Coder CLI, run the following command to create a key that will be used to
+authenticate the provisioner:
+
+```sh
+coder provisioner keys create data-cluster-key --org data-platform
+Successfully created provisioner key data-cluster! Save this authentication token, it will not be shown again.
+
+< key omitted >
+```
+
+Next, start the provisioner with the key on your desired platform. In this
+example, we'll start it using the Coder CLI on a host with Docker. For
+instructions on using other platforms like Kubernetes, see our
+[provisioner documentation](../admin/provisioners.md).
+
+```sh
+export CODER_URL=https://
+export CODER_PROVISIONER_DAEMON_KEY=
+coder provisionerd start --org
+```
+
+### 3. Create a template
+
+Once you've started a provisioner, you can create a template. You'll notice the
+"Create Template" screen now has an organization dropdown:
+
+
+
+### 5. Add members
+
+Navigate to `Deployment->Organizations` to add members to your organization.
+Once added, they will be able to see the organization-specific templates.
+
+
+
+### 6. Create a workspace
+
+Now, users in the data platform organization will see the templates related to
+their organization. Users can be in multiple organizations.
+
+
+
+## Beta
+
+Organizations is in beta. If you encounter any issues, please
+[file an issue](https://github.com/coder/coder/issues/new) or contact your
+account team.
diff --git a/docs/admin/provisioners.md b/docs/admin/provisioners.md
index acbf12f55f54e..31e7001843e61 100644
--- a/docs/admin/provisioners.md
+++ b/docs/admin/provisioners.md
@@ -3,10 +3,10 @@
By default, the Coder server runs
[built-in provisioner daemons](../reference/cli/server.md#provisioner-daemons),
which execute `terraform` during workspace and template builds. However, there
-are sometimes benefits to running external provisioner daemons:
+are often benefits to running external provisioner daemons:
- **Secure build environments:** Run build jobs in isolated containers,
- preventing malicious templates from gaining shell access to the Coder host.
+ preventing malicious templates from gaining sh access to the Coder host.
- **Isolate APIs:** Deploy provisioners in isolated environments (on-prem, AWS,
Azure) instead of exposing APIs (Docker, Kubernetes, VMware) to the Coder
@@ -20,82 +20,101 @@ are sometimes benefits to running external provisioner daemons:
times from the Coder server. See
[Scaling Coder](scaling/scale-utility.md#recent-scale-tests) for more details.
-Each provisioner can run a single
-[concurrent workspace build](scaling/scale-testing.md#control-plane-provisionerd).
+Each provisioner runs a single
+[concurrent workspace build](scaling/scale-testing.md#control-plane-provisioner).
For example, running 30 provisioner containers will allow 30 users to start
workspaces at the same time.
Provisioners are started with the
-[coder provisionerd start](../reference/cli/provisioner_start.md) command.
+[`coder provisioner start`](../reference/cli/provisioner_start.md) command in
+the [full Coder binary](https://github.com/coder/coder/releases). Keep reading
+to learn how to start provisioners via Docker, Kubernetes, Systemd, etc.
## Authentication
-The provisioner daemon must authenticate with your Coder deployment.
+The provisioner daemon must authenticate with your Coder deployment. If you have
+multiple [organizations](./organizations.md), you'll need at least 1 provisioner
+running for each organization.
-Set a
-[provisioner daemon pre-shared key (PSK)](../reference/cli/server.md#--provisioner-daemon-psk)
-on the Coder server and start the provisioner with
-`coder provisionerd start --psk `. If you are
-[installing with Helm](../install/kubernetes.md#install-coder-with-helm), see
-the [Helm example](#example-running-an-external-provisioner-with-helm) below.
+
-> Coder still supports authenticating the provisioner daemon with a
-> [token](../reference/cli/README.md#--token) from a user with the Template
-> Admin or Owner role. This method is deprecated in favor of the PSK, which only
-> has permission to access provisioner daemon APIs. We recommend migrating to
-> the PSK as soon as practical.
+## Scoped Key (Recommended)
-## Types of provisioners
+We recommend creating finely-scoped keys for provisioners. Keys are scoped to an
+organization.
-Provisioners can broadly be categorized by scope: `organization` or `user`. The
-scope of a provisioner can be specified with
-[`-tag=scope=`](../reference/cli/provisioner_start.md#t---tag) when
-starting the provisioner daemon. Only users with at least the
-[Template Admin](../admin/users.md#roles) role or higher may create
-organization-scoped provisioner daemons.
+```sh
+coder provisioner keys create my-key \
+ --org default
-There are two exceptions:
+Successfully created provisioner key my-key! Save this authentication token, it will not be shown again.
-- [Built-in provisioners](../reference/cli/server.md#provisioner-daemons) are
- always organization-scoped.
-- External provisioners started using a
- [pre-shared key (PSK)](../reference/cli/provisioner_start.md#psk) are always
- organization-scoped.
+
+```
-### Organization-Scoped Provisioners
+Or, restrict the provisioner to jobs with specific tags
-**Organization-scoped Provisioners** can pick up build jobs created by any user.
-These provisioners always have the implicit tags `scope=organization owner=""`.
+```sh
+coder provisioner keys create kubernetes-key \
+ --org default \
+ --tag environment=kubernetes
+
+Successfully created provisioner key kubernetes-key! Save this authentication token, it will not be shown again.
-```shell
-coder provisionerd start --org
+
```
-If you omit the `--org` argument, the provisioner will be assigned to the
-default organization.
+To start the provisioner:
-```shell
-coder provisionerd start
+```sh
+export CODER_URL=https://
+export CODER_PROVISIONER_DAEMON_KEY=
+coder provisioner start
```
-### User-scoped Provisioners
+Keep reading to see instructions for running provisioners on
+Kubernetes/Docker/etc.
-**User-scoped Provisioners** can only pick up build jobs created from
-user-tagged templates. Unlike the other provisioner types, any Coder user can
-run user provisioners, but they have no impact unless there exists at least one
-template with the `scope=user` provisioner tag.
+## User Tokens
-```shell
-coder provisionerd start \
- --tag scope=user
+A user account with the role `Template Admin` or `Owner` can start provisioners
+using their user account. This may be beneficial if you are running provisioners
+via [automation](./automation.md).
-# In another terminal, create/push
-# a template that requires user provisioners
-coder templates push on-prem \
- --provisioner-tag scope=user
+```sh
+coder login https://
+coder provisioner start
+```
+
+To start a provisioner with specific tags:
+
+```sh
+coder login https://
+coder provisioner start \
+ --tag environment=kubernetes
```
-### Provisioner Tags
+Note: Any user can start [user-scoped provisioners](#User-scoped-Provisioners),
+but this will also require a template on your deployment with the corresponding
+tags.
+
+## Global PSK
+
+A deployment-wide PSK can be used to authenticate any provisioner. We do not
+recommend this approach anymore, as it makes key rotation or isolating
+provisioners far more difficult. To use a global PSK, set a
+[provisioner daemon pre-shared key (PSK)](../reference/cli/server.md#--provisioner-daemon-psk)
+on the Coder server.
+
+Next, start the provisioner:
+
+```sh
+coder provisioner start --psk
+```
+
+
+
+## Provisioner Tags
You can use **provisioner tags** to control which provisioners can pick up build
jobs from templates (and corresponding workspaces) with matching explicit tags.
@@ -110,10 +129,10 @@ automatically.
For example:
-```shell
+```sh
# Start a provisioner with the explicit tags
# environment=on_prem and datacenter=chicago
-coder provisionerd start \
+coder provisioner start \
--tag environment=on_prem \
--tag datacenter=chicago
@@ -129,6 +148,10 @@ coder templates push on-prem-chicago \
--provisioner-tag datacenter=chicago
```
+Alternatively, a template can target a provisioner via
+[workspace tags](https://github.com/coder/coder/tree/main/examples/workspace-tags)
+inside the Terraform.
+
A provisioner can run a given build job if one of the below is true:
1. A job with no explicit tags can only be run on a provisioner with no explicit
@@ -176,9 +199,59 @@ This is illustrated in the below table:
> copy the output:
>
> ```
-> go test -v -count=1 ./coderd/provisionerdserver/ -test.run='^TestAcquirer_MatchTags/GenTable$'
+> go test -v -count=1 ./coderd/provisionerserver/ -test.run='^TestAcquirer_MatchTags/GenTable$'
> ```
+## Types of provisioners
+
+Provisioners can broadly be categorized by scope: `organization` or `user`. The
+scope of a provisioner can be specified with
+[`-tag=scope=`](../reference/cli/provisioner_start.md#t---tag) when
+starting the provisioner daemon. Only users with at least the
+[Template Admin](../admin/users.md#roles) role or higher may create
+organization-scoped provisioner daemons.
+
+There are two exceptions:
+
+- [Built-in provisioners](../reference/cli/server.md#provisioner-daemons) are
+ always organization-scoped.
+- External provisioners started using a
+ [pre-shared key (PSK)](../reference/cli/provisioner_start.md#psk) are always
+ organization-scoped.
+
+### Organization-Scoped Provisioners
+
+**Organization-scoped Provisioners** can pick up build jobs created by any user.
+These provisioners always have the implicit tags `scope=organization owner=""`.
+
+```sh
+coder provisioner start --org
+```
+
+If you omit the `--org` argument, the provisioner will be assigned to the
+default organization.
+
+```sh
+coder provisioner start
+```
+
+### User-scoped Provisioners
+
+**User-scoped Provisioners** can only pick up build jobs created from
+user-tagged templates. Unlike the other provisioner types, any Coder user can
+run user provisioners, but they have no impact unless there exists at least one
+template with the `scope=user` provisioner tag.
+
+```sh
+coder provisioner start \
+ --tag scope=user
+
+# In another terminal, create/push
+# a template that requires user provisioners
+coder templates push on-prem \
+ --provisioner-tag scope=user
+```
+
## Example: Running an external provisioner with Helm
Coder provides a Helm chart for running external provisioner daemons, which you
@@ -187,21 +260,21 @@ will use in concert with the Helm chart for deploying the Coder server.
1. Create a long, random pre-shared key (PSK) and store it in a Kubernetes
secret
- ```shell
+ ```sh
kubectl create secret generic coder-provisioner-psk --from-literal=psk=`head /dev/urandom | base64 | tr -dc A-Za-z0-9 | head -c 26`
```
1. Modify your Coder `values.yaml` to include
```yaml
- provisionerDaemon:
+ provisioneraemon:
pskSecretName: "coder-provisioner-psk"
```
1. Redeploy Coder with the new `values.yaml` to roll out the PSK. You can omit
`--version ` to also upgrade Coder to the latest version.
- ```shell
+ ```sh
helm upgrade coder coder-v2/coder \
--namespace coder \
--version \
@@ -217,7 +290,7 @@ will use in concert with the Helm chart for deploying the Coder server.
- name: CODER_URL
value: "https://coder.example.com"
replicaCount: 10
- provisionerDaemon:
+ provisioneraemon:
pskSecretName: "coder-provisioner-psk"
tags:
location: auh
@@ -235,7 +308,7 @@ will use in concert with the Helm chart for deploying the Coder server.
1. Install the provisioner daemon chart
- ```shell
+ ```sh
helm install coder-provisioner coder-v2/coder-provisioner \
--namespace coder \
--version \
@@ -244,26 +317,26 @@ will use in concert with the Helm chart for deploying the Coder server.
You can verify that your provisioner daemons have successfully connected to
Coderd by looking for a debug log message that says
- `provisionerd: successfully connected to coderd` from each Pod.
+ `provisioner: successfully connected to coderd` from each Pod.
## Example: Running an external provisioner on a VM
-```shell
+```sh
curl -L https://coder.com/install.sh | sh
export CODER_URL=https://coder.example.com
export CODER_SESSION_TOKEN=your_token
-coder provisionerd start
+coder provisioner start
```
## Example: Running an external provisioner via Docker
-```shell
+```sh
docker run --rm -it \
-e CODER_URL=https://coder.example.com/ \
-e CODER_SESSION_TOKEN=your_token \
--entrypoint /opt/coder \
ghcr.io/coder/coder:latest \
- provisionerd start
+ provisioner start
```
## Disable built-in provisioners
@@ -272,7 +345,7 @@ As mentioned above, the Coder server will run built-in provisioners by default.
This can be disabled with a server-wide
[flag or environment variable](../reference/cli/server.md#provisioner-daemons).
-```shell
+```sh
coder server --provisioner-daemons=0
```
diff --git a/docs/admin/quotas.md b/docs/admin/quotas.md
index 88ca4b27860dc..79cd8f43b2162 100644
--- a/docs/admin/quotas.md
+++ b/docs/admin/quotas.md
@@ -102,5 +102,4 @@ Form will never get held up by quota enforcement.
## Up next
-- [Enterprise](../enterprise.md)
- [Configuring](./configure.md)
diff --git a/docs/admin/rbac.md b/docs/admin/rbac.md
index 86fd46a2bf723..7ca9e3c29131a 100644
--- a/docs/admin/rbac.md
+++ b/docs/admin/rbac.md
@@ -19,5 +19,5 @@ You can set the following permissions:
## Enabling this feature
-This feature is only available with an enterprise license.
-[Learn more](../enterprise.md)
+This feature is only available with an
+[Enterprise or Premium license](https://coder.com/pricing).
diff --git a/docs/admin/upgrade.md b/docs/admin/upgrade.md
index eb24e0f5d5e4f..d9b72f9295dc2 100644
--- a/docs/admin/upgrade.md
+++ b/docs/admin/upgrade.md
@@ -53,7 +53,3 @@ from Winget.
```pwsh
winget install Coder.Coder
```
-
-## Up Next
-
-- [Learn how to enable Enterprise features](../enterprise.md).
diff --git a/docs/admin/users.md b/docs/admin/users.md
index 02832a7e22320..20cca2711af9c 100644
--- a/docs/admin/users.md
+++ b/docs/admin/users.md
@@ -10,7 +10,7 @@ Coder offers these user roles in the community edition:
| | Auditor | User Admin | Template Admin | Owner |
| ----------------------------------------------------- | ------- | ---------- | -------------- | ----- |
| Add and remove Users | | ✅ | | ✅ |
-| Manage groups (enterprise) | | ✅ | | ✅ |
+| Manage groups (premium) | | ✅ | | ✅ |
| Change User roles | | | | ✅ |
| Manage **ALL** Templates | | | ✅ | ✅ |
| View **ALL** Workspaces | | | ✅ | ✅ |
@@ -22,6 +22,16 @@ Coder offers these user roles in the community edition:
A user may have one or more roles. All users have an implicit Member role that
may use personal workspaces.
+## Custom Roles (Premium) (Beta)
+
+Coder v2.16+ deployments can configure custom roles on the
+[Organization](./organizations.md) level.
+
+
+
+> Note: This requires a Premium license.
+> [Contact your account team](https://coder.com/contact) for more details.
+
## Security notes
A malicious Template Admin could write a template that executes commands on the
diff --git a/docs/changelogs/v2.0.0.md b/docs/changelogs/v2.0.0.md
index cfa653900b27b..d245e70819056 100644
--- a/docs/changelogs/v2.0.0.md
+++ b/docs/changelogs/v2.0.0.md
@@ -64,7 +64,7 @@ ben@coder.com!
Stream Kubernetes event logs to the Coder agent logs to reveal Kuernetes-level
issues such as ResourceQuota limitations, invalid images, etc.

-- [OIDC Role Sync](https://coder.com/docs/admin/auth#group-sync-enterprise)
+- [OIDC Role Sync](https://coder.com/docs/admin/auth#group-sync-enterprise-premium)
(Enterprise): Sync roles from your OIDC provider to Coder roles (e.g.
`Template Admin`) (#8595) (@Emyrk)
- Users can convert their accounts from username/password authentication to SSO
diff --git a/docs/contributing/feature-stages.md b/docs/contributing/feature-stages.md
index 26eaf3272b47b..4489c212e25e0 100644
--- a/docs/contributing/feature-stages.md
+++ b/docs/contributing/feature-stages.md
@@ -1,21 +1,34 @@
# Feature stages
-Some Coder features are released as Alpha or Experimental.
+Some Coder features are released in feature stages before they are generally
+available.
-## Alpha features
+If you encounter an issue with any Coder feature, please submit a
+[GitHub issues](https://github.com/coder/coder/issues) or join the
+[Coder Discord](https://discord.gg/coder).
-Alpha features are enabled in all Coder deployments but the feature is subject
-to change, or even be removed. Breaking changes may not be documented in the
-changelog. In most cases, features will only stay in alpha for 1 month.
+## Early access features
-We recommend using [GitHub issues](https://github.com/coder/coder/issues) to
-leave feedback and get support for alpha features.
+Early access features are neither feature-complete nor stable. We do not
+recommend using early access features in production deployments.
+
+Coder releases early access features behind an “unsafe” experiment, where
+they’re accessible but not easy to find.
## Experimental features
These features are disabled by default, and not recommended for use in
production as they may cause performance or stability issues. In most cases,
-features will only stay in experimental for 1-2 weeks of internal testing.
+experimental features are complete, but require further internal testing and
+will stay in the experimental stage for one month.
+
+Coder may make significant changes to experiments or revert features to a
+feature flag at any time.
+
+If you plan to activate an experimental feature, we suggest that you use a
+staging deployment.
+
+You can opt-out of an experiment after you've enabled it.
```yaml
# Enable all experimental features
@@ -27,7 +40,7 @@ coder server --experiments=feature1,feature2
# Alternatively, use the `CODER_EXPERIMENTS` environment variable.
```
-## Available experimental features
+### Available experimental features
@@ -37,3 +50,14 @@ coder server --experiments=feature1,feature2
| `notifications` | Sends notifications via SMTP and webhooks following certain events. | mainline, stable |
+
+## Beta
+
+Beta features are open to the public, but are tagged with a `Beta` label.
+
+They’re subject to minor changes and may contain bugs, but are generally ready
+for use.
+
+## General Availability (GA)
+
+All other features have been tested, are stable, and are enabled by default.
diff --git a/docs/enterprise.md b/docs/enterprise.md
deleted file mode 100644
index 275fcddf60be3..0000000000000
--- a/docs/enterprise.md
+++ /dev/null
@@ -1,56 +0,0 @@
-# Enterprise Features
-
-Coder is free to use and includes some features that are only accessible with a
-paid license. [Contact Sales](https://coder.com/contact) for pricing or
-[get a free trial](https://coder.com/trial).
-
-| Category | Feature | Open Source | Enterprise |
-| --------------- | --------------------------------------------------------------------------------------------------- | :---------: | :--------: |
-| Support | Email, Prioritization | ❌ | ✅ |
-| Scale | [High Availability](./admin/high-availability.md) | ❌ | ✅ |
-| Scale | [Multiple External Auth Providers](./admin/external-auth.md#multiple-external-providers-enterprise) | ❌ | ✅ |
-| Scale | [Isolated Terraform Runners](./admin/provisioners.md) | ❌ | ✅ |
-| Scale | [Workspace Proxies](./admin/workspace-proxies.md) | ❌ | ✅ |
-| Governance | [Audit Logging](./admin/audit-logs.md) | ❌ | ✅ |
-| Governance | [Browser Only Connections](./networking/#browser-only-connections-enterprise) | ❌ | ✅ |
-| Governance | [Groups & Template RBAC](./admin/rbac.md) | ❌ | ✅ |
-| Cost Control | [Quotas](./admin/quotas.md) | ❌ | ✅ |
-| Cost Control | [Max Workspace Lifetime](./workspaces.md#max-lifetime) | ❌ | ✅ |
-| User Management | [Groups](./admin/groups.md) | ❌ | ✅ |
-| User Management | [Group & role sync](./admin/auth.md#group-sync-enterprise) | ❌ | ✅ |
-| User Management | [SCIM](./admin/auth.md#scim) | ❌ | ✅ |
-
-## Adding your license key
-
-There are two ways to add an enterprise license to a Coder deployment: In the
-Coder UI or with the Coder CLI.
-
-### Coder UI
-
-Click Deployment, Licenses, Add a license then drag or select the license file
-with the `jwt` extension.
-
-
-
-### Coder CLI
-
-### Requirements
-
-- Your license key
-- Coder CLI installed
-
-### Instructions
-
-1. Save your license key to disk and make note of the path
-2. Open a terminal
-3. Ensure you are logged into your Coder deployment
-
- `coder login `
-
-4. Run
-
- `coder licenses add -f `
-
-## Up Next
-
-- [Learn how to contribute to Coder](./CONTRIBUTING.md).
diff --git a/docs/faqs.md b/docs/faqs.md
index dceb8ac15b04e..7affd790380ff 100644
--- a/docs/faqs.md
+++ b/docs/faqs.md
@@ -1,14 +1,13 @@
# FAQs
-Frequently asked questions on Coder OSS and Enterprise deployments. These FAQs
-come from our community and enterprise customers, feel free to
+Frequently asked questions on Coder OSS and Premium deployments. These FAQs come
+from our community and enterprise customers, feel free to
[contribute to this page](https://github.com/coder/coder/edit/main/docs/faqs.md).
-### How do I add an enterprise license?
+### How do I add a Premium trial license?
Visit https://coder.com/trial or contact
-[sales@coder.com](mailto:sales@coder.com?subject=License) to get a v2 enterprise
-trial key.
+[sales@coder.com](mailto:sales@coder.com?subject=License) to get a trial key.
You can add a license through the UI or CLI.
diff --git a/docs/guides/configuring-okta.md b/docs/guides/configuring-okta.md
index d52c99a5a7974..d26c09feb7f43 100644
--- a/docs/guides/configuring-okta.md
+++ b/docs/guides/configuring-okta.md
@@ -46,7 +46,7 @@ be sent.
Configure Coder to use these claims for group sync. These claims are present in
the `id_token`. See all configuration options for group sync in the
-[docs](https://coder.com/docs/admin/auth#group-sync-enterprise).
+[docs](https://coder.com/docs/admin/auth#group-sync-enterprise-premium).
```bash
# Add the 'groups' scope.
diff --git a/docs/guides/index.md b/docs/guides/index.md
index 40d842685df44..c1768210d0d91 100644
--- a/docs/guides/index.md
+++ b/docs/guides/index.md
@@ -1,7 +1,7 @@
# Guides and Tutorials
Here you can find a list of employee-written guides on Coder for OSS and
-Enterprise. These tutorials are hosted on our
+Premium. These tutorials are hosted on our
[Github](https://github.com/coder/coder/) where you can leave feedback or
request new topics to be covered.
diff --git a/docs/guides/using-organizations.md b/docs/guides/using-organizations.md
deleted file mode 100644
index 88b52313db71a..0000000000000
--- a/docs/guides/using-organizations.md
+++ /dev/null
@@ -1,135 +0,0 @@
-# Using Organizations
-
-> Note: Organizations is still under active development and requires a
-> non-standard enterprise license to use. Do not use organizations on your
-> production instance!
->
-> For more details, [contact your account team](https://coder.com/contact).
-
-Organizations allow you to run a Coder deployment with multiple platform teams,
-all with uniquely scoped templates, provisioners, users, groups, and workspaces.
-
-## Prerequisites
-
-- Coder deployment with non-standard license with Organizations enabled
- ([contact your account team](https://coder.com/contact))
-- User with `Owner` role
-- Coder CLI installed on local machine
-
-## Switch to the preview image and enable the experiment
-
-To try the latest organizations features, switch to a preview image in your Helm
-chart and enable the
-[experimental flag](../reference/cli/server.md#--experiments).
-
-For example, with Kubernetes, set the following in your `values.yaml`:
-
-```yaml
-coderd:
- image:
- repo: ghcr.io/coder/coder-preview
- tag: orgs-preview-aug-16
- env:
- - name: CODER_EXPERIMENTS
- value: multi-organization
-```
-
-> See all
-> [preview images](https://github.com/coder/coder/pkgs/container/coder-preview)
-> in GitHub. Preview images prefixed with `main-` expire after a week.
-
-Then, upgrade your deployment:
-
-```sh
-helm upgrade coder coder-v2/coder -f values.yaml
-```
-
-## The default organization
-
-All Coder deployments start with one organization called `Default`.
-
-To edit the organization details, navigate to `Deployment -> Organizations` in
-the top bar:
-
-
-
-From there, you can manage the name, icon, description, users, and groups:
-
-
-
-## Guide: Your first organization
-
-### 1. Create the organization
-
-Within the sidebar, click `New organization` to create an organization. In this
-example, we'll create the `data-platform` org.
-
-
-
-From there, let's deploy a provisioner and template for this organization.
-
-### 2. Deploy a provisioner
-
-[Provisioners](../admin/provisioners.md) are organization-scoped and are
-responsible for executing Terraform/OpenTofu to provision the infrastructure for
-workspaces and testing templates. Before creating templates, we must deploy at
-least one provisioner as the built-in provisioners are scoped to the default
-organization.
-
-using Coder CLI, run the following command to create a key that will be used to
-authenticate the provisioner:
-
-```sh
-coder provisioner keys create data-cluster-key --org data-platform
-Successfully created provisioner key data-cluster! Save this authentication token, it will not be shown again.
-
-< key omitted >
-```
-
-Next, start the provisioner with the key on your desired platform. In this
-example, we'll start it using the Coder CLI on a host with Docker. For
-instructions on using other platforms like Kubernetes, see our
-[provisioner documentation](../admin/provisioners.md).
-
-```sh
-export CODER_URL=https://
-export CODER_PROVISIONER_DAEMON_KEY=
-coder provisionerd start --org
-```
-
-### 3. Create a template
-
-Once you've started a provisioner, you can create a template. You'll notice the
-"Create Template" screen now has an organization dropdown:
-
-
-
-### 5. Add members
-
-Navigate to `Deployment->Organizations` to add members to your organization.
-Once added, they will be able to see the organization-specific templates.
-
-
-
-### 6. Create a workspace
-
-Now, users in the data platform organization will see the templates related to
-their organization. Users can be in multiple organizations.
-
-
-
-## Planned work
-
-Organizations is under active development. The work is planned before
-organizations is generally available:
-
-- View provisioner health via the Coder UI
-- Custom Role support in Coder UI
-- Per-organization quotas
-- Improved visibility of organization-specific resources throughout the UI
-- Sync OIDC claims to auto-assign users to organizations / roles + SCIM support
-
-## Support & Feedback
-
-[Contact your account team](https://coder.com/contact) if you have any questions
-or feedback.
diff --git a/docs/images/add-license-ui.png b/docs/images/add-license-ui.png
index 03ff419d15a59..837698908e8f1 100644
Binary files a/docs/images/add-license-ui.png and b/docs/images/add-license-ui.png differ
diff --git a/docs/images/admin/organizations/custom-roles.png b/docs/images/admin/organizations/custom-roles.png
new file mode 100644
index 0000000000000..505fc5730ddd4
Binary files /dev/null and b/docs/images/admin/organizations/custom-roles.png differ
diff --git a/docs/images/guides/using-organizations/default-organization.png b/docs/images/admin/organizations/default-organization.png
similarity index 100%
rename from docs/images/guides/using-organizations/default-organization.png
rename to docs/images/admin/organizations/default-organization.png
diff --git a/docs/images/guides/using-organizations/deployment-organizations.png b/docs/images/admin/organizations/deployment-organizations.png
similarity index 100%
rename from docs/images/guides/using-organizations/deployment-organizations.png
rename to docs/images/admin/organizations/deployment-organizations.png
diff --git a/docs/images/admin/organizations/diagram.png b/docs/images/admin/organizations/diagram.png
new file mode 100644
index 0000000000000..b7d232c274b42
Binary files /dev/null and b/docs/images/admin/organizations/diagram.png differ
diff --git a/docs/images/admin/organizations/group-sync.png b/docs/images/admin/organizations/group-sync.png
new file mode 100644
index 0000000000000..a4013f2f15559
Binary files /dev/null and b/docs/images/admin/organizations/group-sync.png differ
diff --git a/docs/images/guides/using-organizations/new-organization.png b/docs/images/admin/organizations/new-organization.png
similarity index 100%
rename from docs/images/guides/using-organizations/new-organization.png
rename to docs/images/admin/organizations/new-organization.png
diff --git a/docs/images/guides/using-organizations/organization-members.png b/docs/images/admin/organizations/organization-members.png
similarity index 100%
rename from docs/images/guides/using-organizations/organization-members.png
rename to docs/images/admin/organizations/organization-members.png
diff --git a/docs/images/admin/organizations/role-sync.png b/docs/images/admin/organizations/role-sync.png
new file mode 100644
index 0000000000000..1b0fafb39fae1
Binary files /dev/null and b/docs/images/admin/organizations/role-sync.png differ
diff --git a/docs/images/guides/using-organizations/template-org-picker.png b/docs/images/admin/organizations/template-org-picker.png
similarity index 100%
rename from docs/images/guides/using-organizations/template-org-picker.png
rename to docs/images/admin/organizations/template-org-picker.png
diff --git a/docs/images/guides/using-organizations/workspace-list.png b/docs/images/admin/organizations/workspace-list.png
similarity index 100%
rename from docs/images/guides/using-organizations/workspace-list.png
rename to docs/images/admin/organizations/workspace-list.png
diff --git a/docs/images/icons/licensing.svg b/docs/images/icons/licensing.svg
new file mode 100644
index 0000000000000..6e876fd359583
--- /dev/null
+++ b/docs/images/icons/licensing.svg
@@ -0,0 +1,3 @@
+
diff --git a/docs/images/icons/orgs.svg b/docs/images/icons/orgs.svg
new file mode 100644
index 0000000000000..ff65ea99664b6
--- /dev/null
+++ b/docs/images/icons/orgs.svg
@@ -0,0 +1,3 @@
+
diff --git a/docs/licensing.md b/docs/licensing.md
new file mode 100644
index 0000000000000..e34e813c2a354
--- /dev/null
+++ b/docs/licensing.md
@@ -0,0 +1,47 @@
+# Licensing
+
+Some features are only accessible with a Premium or Enterprise license. See our
+[pricing page](https://coder.com/pricing) for more details. To try Premium
+features, you can [request a trial](https://coder.com/trial) or
+[contact sales](https://coder.com/contact).
+
+
+
+> If you are an existing customer, you can learn more our new Premium plan in
+> the [Coder v2.16 blog post](https://coder.com/blog/release-recap-2-16-0)
+
+
+
+## Adding your license key
+
+There are two ways to add a license to a Coder deployment:
+
+
+
+### Coder UI
+
+First, ensure you have a license key
+([request a trial](https://coder.com/trial)).
+
+With an `Owner` account, navigate to `Deployment -> Licenses`, `Add a license`
+then drag or select the license file with the `jwt` extension.
+
+
+
+### Coder CLI
+
+First, ensure you have a license key
+([request a trial](https://coder.com/trial)) and the
+[Coder CLI](./install/index.md) installed.
+
+1. Save your license key to disk and make note of the path
+2. Open a terminal
+3. Ensure you are logged into your Coder deployment
+
+ `coder login `
+
+4. Run
+
+ `coder licenses add -f `
+
+
diff --git a/docs/manifest.json b/docs/manifest.json
index 1826c6b62685e..7db5fc142191d 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -282,7 +282,7 @@
"title": "Process Logging",
"description": "Audit commands in workspaces with exectrace",
"path": "./templates/process-logging.md",
- "state": "enterprise"
+ "state": ["enterprise", "premium"]
},
{
"title": "Icons",
@@ -393,14 +393,21 @@
"description": "Learn how to manage user groups",
"path": "./admin/groups.md",
"icon_path": "./images/icons/group.svg",
- "state": "enterprise"
+ "state": ["enterprise", "premium"]
},
{
- "title": "RBAC",
- "description": "Learn how to use the role based access control",
+ "title": "Organizations",
+ "description": "Learn how to manage organizations",
+ "path": "./admin/organizations.md",
+ "icon_path": "./images/icons/orgs.svg",
+ "state": ["premium"]
+ },
+ {
+ "title": "Template RBAC",
+ "description": "Learn how to use the role based access control against templates",
"path": "./admin/rbac.md",
"icon_path": "./images/icons/rbac.svg",
- "state": "enterprise"
+ "state": ["enterprise", "beta"]
},
{
"title": "Configuration",
@@ -443,14 +450,14 @@
"description": "Run provisioners isolated from the Coder server",
"path": "./admin/provisioners.md",
"icon_path": "./images/icons/queue.svg",
- "state": "enterprise"
+ "state": ["enterprise", "premium"]
},
{
"title": "Workspace Proxies",
"description": "Run geo distributed workspace proxies",
"path": "./admin/workspace-proxies.md",
"icon_path": "./images/icons/networking.svg",
- "state": "enterprise"
+ "state": ["enterprise", "premium"]
},
{
"title": "Application Logs",
@@ -463,21 +470,21 @@
"description": "Learn how to use Audit Logs in your Coder deployment",
"path": "./admin/audit-logs.md",
"icon_path": "./images/icons/radar.svg",
- "state": "enterprise"
+ "state": ["enterprise", "premium"]
},
{
"title": "Quotas",
"description": "Learn how to use Workspace Quotas in Coder",
"path": "./admin/quotas.md",
"icon_path": "./images/icons/dollar.svg",
- "state": "enterprise"
+ "state": ["enterprise", "premium"]
},
{
"title": "High Availability",
"description": "Learn how to configure Coder for High Availability",
"path": "./admin/high-availability.md",
"icon_path": "./images/icons/hydra.svg",
- "state": "enterprise"
+ "state": ["enterprise", "premium"]
},
{
"title": "Prometheus",
@@ -490,7 +497,7 @@
"description": "Learn how to configure the appearance of Coder",
"path": "./admin/appearance.md",
"icon_path": "./images/icons/info.svg",
- "state": "enterprise"
+ "state": ["enterprise", "premium"]
},
{
"title": "Telemetry",
@@ -503,7 +510,7 @@
"description": "Learn how to encrypt sensitive data at rest in Coder",
"path": "./admin/encryption.md",
"icon_path": "./images/icons/lock.svg",
- "state": "enterprise"
+ "state": ["enterprise", "premium"]
},
{
"title": "Deployment Health",
@@ -521,23 +528,23 @@
"title": "Slack Notifications",
"description": "Learn how to setup Slack notifications",
"path": "./admin/notifications/slack.md",
- "state": "beta"
+ "state": ["beta"]
},
{
"title": "Microsoft Teams Notifications",
"description": "Learn how to setup Microsoft Teams notifications",
"path": "./admin/notifications/teams.md",
- "state": "beta"
+ "state": ["beta"]
}
]
}
]
},
{
- "title": "Enterprise",
- "description": "Learn how to enable Enterprise features",
- "path": "./enterprise.md",
- "icon_path": "./images/icons/group.svg"
+ "title": "Licensing",
+ "description": "Learn how to enable Premium features",
+ "path": "./licensing.md",
+ "icon_path": "./images/icons/licensing.svg"
},
{
"title": "Contributing",
@@ -1337,11 +1344,6 @@
"title": "Cloning Git Repositories",
"description": "Automatically clone Git repositories into your workspace",
"path": "./guides/cloning-git-repositories.md"
- },
- {
- "title": "Using Organizations",
- "description": "Learn how to use our (early access) Organizations functionality",
- "path": "./guides/using-organizations.md"
}
]
}
diff --git a/docs/networking/index.md b/docs/networking/index.md
index d4abddc5718c8..781cb6aae6dca 100644
--- a/docs/networking/index.md
+++ b/docs/networking/index.md
@@ -157,10 +157,10 @@ $ coder server --derp-config-path derpmap.json
The dashboard (and web apps opened through the dashboard) are served from the
coder server, so they can only be geo-distributed with High Availability mode in
-our Enterprise Edition. [Reach out to Sales](https://coder.com/contact) to learn
-more.
+our Enterprise and Premium Editions.
+[Reach out to Sales](https://coder.com/contact) to learn more.
-## Browser-only connections (enterprise)
+## Browser-only connections (enterprise) (premium)
Some Coder deployments require that all access is through the browser to comply
with security policies. In these cases, pass the `--browser-only` flag to
diff --git a/docs/networking/port-forwarding.md b/docs/networking/port-forwarding.md
index 89454b8258e3d..b0e178708a9de 100644
--- a/docs/networking/port-forwarding.md
+++ b/docs/networking/port-forwarding.md
@@ -129,7 +129,7 @@ resource uses a different method of authentication and **is not impacted by the
template's maximum sharing level**, nor the level of a shared port that points
to the app.
-### Configure maximum port sharing level (enterprise)
+### Configure maximum port sharing level (enterprise) (premium)
Enterprise-licensed template admins can control the maximum port sharing level
for workspaces under a given template in the template settings. By default, the
diff --git a/docs/workspaces.md b/docs/workspaces.md
index 2968420022770..1ce503218b699 100644
--- a/docs/workspaces.md
+++ b/docs/workspaces.md
@@ -83,7 +83,7 @@ coder_app.

-### Autostop requirement (enterprise)
+### Autostop requirement (enterprise) (premium)
Autostop requirement is a template setting that determines how often workspaces
using the template must automatically stop. Autostop requirement ignores any
@@ -113,7 +113,7 @@ Autostop requirement is disabled when the template is using the deprecated max
lifetime feature. Templates can choose to use a max lifetime or an autostop
requirement during the deprecation period, but only one can be used at a time.
-### User quiet hours (enterprise)
+### User quiet hours (enterprise) (premium)
User quiet hours can be configured in the user's schedule settings page.
Workspaces on templates with an autostop requirement will only be forcibly
diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go
index 5127e6ec0887f..e0b8170c6ec2a 100644
--- a/enterprise/coderd/coderd.go
+++ b/enterprise/coderd/coderd.go
@@ -448,7 +448,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
// with the below route, we need to register this route without any mounts or groups to make both work.
r.With(
apiKeyMiddleware,
- httpmw.RequireExperiment(api.AGPL.Experiments, codersdk.ExperimentNotifications),
httpmw.ExtractNotificationTemplateParam(options.Database),
).Put("/notifications/templates/{notification_template}/method", api.updateNotificationTemplateMethod)
})
diff --git a/enterprise/coderd/notifications_test.go b/enterprise/coderd/notifications_test.go
index 5546bec1dcb79..b71bde86a5736 100644
--- a/enterprise/coderd/notifications_test.go
+++ b/enterprise/coderd/notifications_test.go
@@ -23,7 +23,6 @@ func createOpts(t *testing.T) *coderdenttest.Options {
t.Helper()
dt := coderdtest.DeploymentValues(t)
- dt.Experiments = []string{string(codersdk.ExperimentNotifications)}
return &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: dt,
diff --git a/enterprise/coderd/provisionerkeys.go b/enterprise/coderd/provisionerkeys.go
index ea89538965afa..08556e2eb24c2 100644
--- a/enterprise/coderd/provisionerkeys.go
+++ b/enterprise/coderd/provisionerkeys.go
@@ -147,9 +147,13 @@ func (api *API) provisionerKeyDaemons(rw http.ResponseWriter, r *http.Request) {
pkDaemons := []codersdk.ProvisionerKeyDaemons{}
for _, key := range sdkKeys {
- // currently we exclude user-auth from this list
+ // The key.OrganizationID for the `user-auth` key is hardcoded to
+ // the default org in the database and we are overwriting it here
+ // to be the correct org we used to query the list.
+ // This will be changed when we update the `user-auth` keys to be
+ // directly tied to a user ID.
if key.ID.String() == codersdk.ProvisionerKeyIDUserAuth {
- continue
+ key.OrganizationID = organization.ID
}
daemons := []codersdk.ProvisionerDaemon{}
for _, daemon := range recentDaemons {
diff --git a/scripts/apidocgen/postprocess/main.go b/scripts/apidocgen/postprocess/main.go
index 0af4f411a1e8f..a70a2091154c9 100644
--- a/scripts/apidocgen/postprocess/main.go
+++ b/scripts/apidocgen/postprocess/main.go
@@ -169,12 +169,12 @@ func writeDocs(sections [][]byte) error {
// Update manifest.json
type route struct {
- Title string `json:"title,omitempty"`
- Description string `json:"description,omitempty"`
- Path string `json:"path,omitempty"`
- IconPath string `json:"icon_path,omitempty"`
- State string `json:"state,omitempty"`
- Children []route `json:"children,omitempty"`
+ Title string `json:"title,omitempty"`
+ Description string `json:"description,omitempty"`
+ Path string `json:"path,omitempty"`
+ IconPath string `json:"icon_path,omitempty"`
+ State []string `json:"state,omitempty"`
+ Children []route `json:"children,omitempty"`
}
type manifest struct {
diff --git a/scripts/clidocgen/main.go b/scripts/clidocgen/main.go
index a5a48a9311df2..68b97b7f19a3c 100644
--- a/scripts/clidocgen/main.go
+++ b/scripts/clidocgen/main.go
@@ -14,12 +14,12 @@ import (
// route is an individual page object in the docs manifest.json.
type route struct {
- Title string `json:"title,omitempty"`
- Description string `json:"description,omitempty"`
- Path string `json:"path,omitempty"`
- IconPath string `json:"icon_path,omitempty"`
- State string `json:"state,omitempty"`
- Children []route `json:"children,omitempty"`
+ Title string `json:"title,omitempty"`
+ Description string `json:"description,omitempty"`
+ Path string `json:"path,omitempty"`
+ IconPath string `json:"icon_path,omitempty"`
+ State []string `json:"state,omitempty"`
+ Children []route `json:"children,omitempty"`
}
// manifest describes the entire documentation index.
diff --git a/site/src/components/FeatureStageBadge/FeatureStageBadge.tsx b/site/src/components/FeatureStageBadge/FeatureStageBadge.tsx
index 52cdfaeb01a11..763b180d03bbe 100644
--- a/site/src/components/FeatureStageBadge/FeatureStageBadge.tsx
+++ b/site/src/components/FeatureStageBadge/FeatureStageBadge.tsx
@@ -10,7 +10,7 @@ import { docs } from "utils/docs";
* All types of feature that we are currently supporting. Defined as record to
* ensure that we can't accidentally make typos when writing the badge text.
*/
-const featureStageBadgeTypes = {
+export const featureStageBadgeTypes = {
beta: "beta",
experimental: "experimental",
} as const satisfies Record;
diff --git a/site/src/components/Paywall/Paywall.tsx b/site/src/components/Paywall/Paywall.tsx
index 4ab955c856a3c..ae940ce515836 100644
--- a/site/src/components/Paywall/Paywall.tsx
+++ b/site/src/components/Paywall/Paywall.tsx
@@ -58,7 +58,7 @@ export const Paywall: FC = ({
diff --git a/site/src/pages/SetupPage/SetupPageView.tsx b/site/src/pages/SetupPage/SetupPageView.tsx
index 3b0a7ea44b9a0..a4b0536ae0b85 100644
--- a/site/src/pages/SetupPage/SetupPageView.tsx
+++ b/site/src/pages/SetupPage/SetupPageView.tsx
@@ -211,7 +211,7 @@ export const SetupPageView: FC = ({
quotas, and more.
diff --git a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx
index 49f01f1f00936..c67737fc00530 100644
--- a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx
+++ b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx
@@ -99,6 +99,7 @@ export const NotificationsPage: FC = () => {
title="Notifications"
description="Configure your notification preferences. Icons on the right of each notification indicate delivery method, either SMTP or Webhook."
layout="fluid"
+ featureStage="beta"
>
{ready ? (
diff --git a/site/src/pages/UserSettingsPage/Section.tsx b/site/src/pages/UserSettingsPage/Section.tsx
index edc2740537fbc..8c52aca1eb9cb 100644
--- a/site/src/pages/UserSettingsPage/Section.tsx
+++ b/site/src/pages/UserSettingsPage/Section.tsx
@@ -1,4 +1,9 @@
import type { Interpolation, Theme } from "@emotion/react";
+import {
+ FeatureStageBadge,
+ type featureStageBadgeTypes,
+} from "components/FeatureStageBadge/FeatureStageBadge";
+import { Stack } from "components/Stack/Stack";
import type { FC, ReactNode } from "react";
type SectionLayout = "fixed" | "fluid";
@@ -13,6 +18,7 @@ export interface SectionProps {
layout?: SectionLayout;
className?: string;
children?: ReactNode;
+ featureStage?: keyof typeof featureStageBadgeTypes;
}
export const Section: FC = ({
@@ -24,6 +30,7 @@ export const Section: FC = ({
className = "",
children,
layout = "fixed",
+ featureStage,
}) => {
return (
@@ -32,16 +39,25 @@ export const Section: FC = ({