diff --git a/pkg/extension/action.go b/pkg/extension/action.go
new file mode 100644
index 0000000000000000000000000000000000000000..43e4341bd3b1de027acc4b91a9d94aabbb84aba1
--- /dev/null
+++ b/pkg/extension/action.go
@@ -0,0 +1,72 @@
+package extension
+
+import (
+	"git.perx.ru/perxis/perxis-go/pkg/references"
+	pb "git.perx.ru/perxis/perxis-go/proto/extensions"
+	"github.com/mitchellh/mapstructure"
+)
+
+const ActionsCollectionID = "space_actions"
+
+type (
+	ActionKind = pb.Action_Kind
+	//ActionRequest  = pb.ActionRequest
+	//ActionResponse = pb.ActionResponse
+)
+
+const (
+	ResponseDone               = pb.ActionResponse_DONE
+	ResponseError              = pb.ActionResponse_ERROR
+	ResponsePending            = pb.ActionResponse_PENDING
+	ResponseInProgress         = pb.ActionResponse_IN_PROGRESS
+	ResponseParametersRequired = pb.ActionResponse_PARAMETERS_REQUIRED
+)
+
+type Action struct {
+	Extension        string                  `mapstructure:"extension,omitempty"`   // Расширение
+	Action           string                  `mapstructure:"action,omitempty"`      // Идентификатор действия
+	Name             string                  `mapstructure:"name,omitempty"`        // Название действия для отображения в интерфейсе (пункт меню, кнопка).
+	Description      string                  `mapstructure:"description,omitempty"` // Описание действия для отображения в интерфейсе
+	Icon             string                  `mapstructure:"icon,omitempty"`        // Название иконки для отображения действия в интерфейсе
+	Image            *references.Reference   `mapstructure:"image,omitempty"`       // Изображение для отображения в действия в интерфейсе
+	Groups           []string                `mapstructure:"groups,omitempty"`      // Группы отображения действия в интерфейсе
+	Kind             ActionKind              `mapstructure:"kind,omitempty"`        // Указывает на что направлено действие
+	Classes          []string                `mapstructure:"classes,omitempty"`     // Классы данных к которым применимо действие (название коллекций или специальных групп в рамках которых данное действие применимо)
+	Refs             []*references.Reference `mapstructure:"refs,omitempty"`        // Ссылки на записи используемые для выполнения действия (назначение ссылок зависит от действия и расширения)
+	ParamsCollection string                  `mapstructure:"params_collection,omitempty"`
+	Request          *ActionRequest          `mapstructure:"request,omitempty"`           // Параметры запроса (используется в случае `ActionResponse.next`)
+	NavigationAction bool                    `mapstructure:"navigation_action,omitempty"` // Флаг указывающий что действие переносить пользователя в другую часть интерфейса, а не отправляет запрос на сервер
+	NavigationRoute  string                  `mapstructure:"navigation_route,omitempty"`
+}
+
+func ActionToMap(action *Action) map[string]interface{} {
+	res := make(map[string]interface{})
+	mapstructure.Decode(action, &res)
+	res["kind"] = int64(action.Kind.Number())
+	return res
+}
+
+func ActionFromMap(d map[string]interface{}) (*Action, error) {
+	var action Action
+	err := mapstructure.Decode(d, &action)
+	return &action, err
+}
+
+func ActionFromPB(a *pb.Action) *Action {
+	return &Action{
+		Extension:        a.Extension,
+		Action:           a.Action,
+		Name:             a.Name,
+		Description:      a.Description,
+		Icon:             a.Icon,
+		Image:            references.ReferenceFromPB(a.Image),
+		Groups:           a.Groups,
+		Kind:             a.Kind,
+		Classes:          a.Classes,
+		Refs:             references.ReferenceListFromPB(a.Refs),
+		ParamsCollection: a.ParamsCollection,
+		Request:          a.Request,
+		NavigationAction: a.NavigationAction,
+		NavigationRoute:  a.NavigationRoute,
+	}
+}
diff --git a/pkg/extension/client.go b/pkg/extension/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..e9e5dff05f807b16b5700d71c962d39d84f625d6
--- /dev/null
+++ b/pkg/extension/client.go
@@ -0,0 +1,77 @@
+package extension
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	pb "git.perx.ru/perxis/perxis-go/proto/extensions"
+	"google.golang.org/grpc"
+)
+
+type Client struct {
+	client pb.ExtensionClient
+}
+
+func NewClient(conn *grpc.ClientConn) *Client {
+	return &Client{
+		client: pb.NewExtensionClient(conn),
+	}
+}
+
+func (c Client) GetDescriptor() *ExtensionDescriptor {
+	return nil
+}
+
+type results interface {
+	GetResults() []*RequestResult
+}
+
+func getErrors(out results, err, wrapErr error) error {
+	if err != nil {
+		return err
+	}
+
+	res := out.GetResults()
+
+	var errs []error
+	for _, r := range res {
+		if r.State == RequestError {
+			err := errors.New(r.GetError())
+			if msg := r.GetMsg(); msg != "" {
+				err = errors.WithDetail(err, msg)
+			}
+			errs = append(errs, ExtensionError(err, r.Extension))
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(wrapErr, errs...)
+	}
+
+	return nil
+}
+
+func (c Client) Install(ctx context.Context, in *InstallRequest) error {
+	out, err := c.client.Install(ctx, in)
+	return getErrors(out, err, ErrInstall)
+}
+
+func (c Client) Check(ctx context.Context, in *CheckRequest) error {
+	out, err := c.client.Check(ctx, in)
+	return getErrors(out, err, ErrCheck)
+}
+
+func (c Client) Update(ctx context.Context, in *UpdateRequest) error {
+	out, err := c.client.Update(ctx, in)
+	return getErrors(out, err, ErrUpdate)
+}
+
+func (c Client) Uninstall(ctx context.Context, in *UninstallRequest) error {
+	out, err := c.client.Uninstall(ctx, in)
+	return getErrors(out, err, ErrUninstall)
+}
+
+func (c Client) Action(ctx context.Context, in *ActionRequest) (*ActionResponse, error) {
+	out, err := c.client.Action(ctx, in)
+	return out, err
+}
diff --git a/pkg/extension/client_test.go b/pkg/extension/client_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d00032a5cb5f944a8dc4a3b6114d1fffaf14dbf7
--- /dev/null
+++ b/pkg/extension/client_test.go
@@ -0,0 +1,83 @@
+package extension
+
+import (
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestGetErrors(t *testing.T) {
+
+	getOut := func(results ...*RequestResult) results { return &InstallResponse{Results: results} }
+
+	tests := []struct {
+		name     string
+		out      results
+		err      error
+		wrapErr  error
+		checkErr func(t *testing.T, err error)
+	}{
+		{
+			name: "no errors",
+			out:  getOut(&RequestResult{State: RequestOK, Extension: "a"}),
+		},
+		{
+			name:    "no error",
+			out:     getOut(&RequestResult{State: RequestOK, Extension: "a"}),
+			wrapErr: ErrInstall,
+		},
+		{
+			name:    "one errored result",
+			out:     getOut(&RequestResult{State: RequestError, Extension: "a", Error: "some err", Msg: "Ошибка"}),
+			wrapErr: ErrInstall,
+			checkErr: func(t *testing.T, err error) {
+				assert.ErrorIs(t, err, ErrInstall)
+				errs := errors.GetErrors(err)
+				require.Len(t, errs, 1)
+
+				extErr := errs[0]
+				assert.Equal(t, "a", ExtensionFromError(extErr))
+				assert.Equal(t, "some err", extErr.Error())
+				assert.Equal(t, "Ошибка", errors.GetDetail(extErr))
+			},
+		},
+		{
+			name: "multiple results, some of them errored",
+			out: getOut(
+				&RequestResult{State: RequestError, Extension: "a", Error: "some err a", Msg: "Ошибка А"},
+				&RequestResult{State: RequestOK, Extension: "b"},
+				&RequestResult{State: RequestError, Extension: "c", Error: "some err c", Msg: "Ошибка С"},
+				&RequestResult{State: RequestOK, Extension: "d"},
+			),
+			wrapErr: ErrInstall,
+			checkErr: func(t *testing.T, err error) {
+				assert.ErrorIs(t, err, ErrInstall)
+				errs := errors.GetErrors(err)
+				require.Len(t, errs, 2)
+
+				extErr1 := errs[0]
+				assert.Equal(t, "a", ExtensionFromError(extErr1))
+				assert.Equal(t, "some err a", extErr1.Error())
+				assert.Equal(t, "Ошибка А", errors.GetDetail(extErr1))
+
+				extErr2 := errs[1]
+				assert.Equal(t, "c", ExtensionFromError(extErr2))
+				assert.Equal(t, "some err c", extErr2.Error())
+				assert.Equal(t, "Ошибка С", errors.GetDetail(extErr2))
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := getErrors(tt.out, tt.err, tt.wrapErr)
+			if tt.checkErr != nil {
+				require.Error(t, err)
+				tt.checkErr(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
diff --git a/pkg/extension/extension.go b/pkg/extension/extension.go
new file mode 100644
index 0000000000000000000000000000000000000000..63691c62c050d109daea0c68c003f4fba1d9ca0e
--- /dev/null
+++ b/pkg/extension/extension.go
@@ -0,0 +1,109 @@
+package extension
+
+import (
+	"context"
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/content"
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	pb "git.perx.ru/perxis/perxis-go/proto/extensions"
+)
+
+const (
+	RequestOK                 = pb.ExtensionRequestResult_OK
+	RequestError              = pb.ExtensionRequestResult_ERROR
+	RequestPending            = pb.ExtensionRequestResult_PENDING
+	RequestInProgress         = pb.ExtensionRequestResult_IN_PROGRESS
+	RequestParametersRequires = pb.ExtensionRequestResult_PARAMETERS_REQUIRED
+
+	ExtensionsCollectionID = "space_extensions"
+
+	StatePending    = pb.SpaceExtensions_PENDING
+	StateInstalled  = pb.SpaceExtensions_INSTALLED
+	StateInProgress = pb.SpaceExtensions_IN_PROGRESS
+	StateFail       = pb.SpaceExtensions_FAIL
+)
+
+type (
+	InstallRequest    = pb.InstallRequest
+	InstallResponse   = pb.InstallResponse
+	CheckRequest      = pb.CheckRequest
+	CheckResponse     = pb.CheckResponse
+	UpdateRequest     = pb.UpdateRequest
+	UpdateResponse    = pb.UpdateResponse
+	UninstallRequest  = pb.UninstallRequest
+	UninstallResponse = pb.UninstallResponse
+	RequestResult     = pb.ExtensionRequestResult
+
+	ActionRequest  = pb.ActionRequest
+	ActionResponse = pb.ActionResponse
+
+	ExtensionDescriptor = pb.ExtensionDescriptor
+)
+
+var (
+	ErrStart = errors.New("start failed")
+	ErrStop  = errors.New("stop failed")
+
+	ErrInstall   = errors.New("install failed")
+	ErrUpdate    = errors.New("update failed")
+	ErrCheck     = errors.New("check failed")
+	ErrUninstall = errors.New("uninstall failed")
+
+	ErrUpdateAvailable  = errors.New("update available")
+	ErrNotInstalled     = errors.New("not installed")
+	ErrUnknownExtension = errors.New("unknown extension")
+)
+
+// Runnable описывает интерфейс сервиса с запуском и остановкой. Вызывается сервером расширений
+type Runnable interface {
+	Start() error
+	Stop() error
+}
+
+// Extension описывает интерфейс расширения Perxis
+type Extension interface {
+	// GetDescriptor возвращает описание расширения
+	GetDescriptor() *ExtensionDescriptor
+
+	// Install вызывается при установке расширения в пространство
+	Install(ctx context.Context, in *InstallRequest) error
+
+	// Check вызывается для проверки состояния расширения
+	Check(ctx context.Context, in *CheckRequest) error
+
+	// Update вызывается для обновления вресии расширения
+	Update(ctx context.Context, in *UpdateRequest) error
+
+	// Uninstall вызывается для удаления расширения из пространства
+	Uninstall(ctx context.Context, in *UninstallRequest) error
+
+	// Action вызывается для выполнения расширением действия
+	Action(ctx context.Context, in *ActionRequest) (*ActionResponse, error)
+}
+
+func CheckInstalled(ctx context.Context, content *content.Content, spaceID, envID, extension string) (bool, error) {
+	res, _, err := content.Items.Find(ctx, spaceID, envID, ExtensionsCollectionID,
+		&items.Filter{Q: []string{fmt.Sprintf("extension == '%s'", extension)}})
+
+	if err != nil {
+		return false, err
+	}
+
+	if len(res) == 0 || res[0].Data["extension_state"] != int64(StateInstalled) {
+		return false, nil
+	}
+
+	return true, nil
+}
+
+func ExtensionError(err error, ext string) error {
+	return errors.WithContext(err, "extension", ext)
+}
+
+func ExtensionFromError(err error) string {
+	v, _ := errors.ContextKey(err, "extension")
+	ext, _ := v.(string)
+	return ext
+}
diff --git a/pkg/extension/key.go b/pkg/extension/key.go
new file mode 100644
index 0000000000000000000000000000000000000000..a93bafe2fc72fd39e80d617fc548512f6f61e663
--- /dev/null
+++ b/pkg/extension/key.go
@@ -0,0 +1,55 @@
+package extension
+
+import (
+	"crypto/sha512"
+	"encoding/base64"
+	"strings"
+)
+
+// KeyFunc функция получения ключа доступа к указанному пространству
+type KeyFunc func(spaceID string) string
+type SignatureFunc func(spaceID string) string
+
+func NamedSignedKey(name string, signature any) KeyFunc {
+	switch v := signature.(type) {
+	case string:
+		return func(spaceID string) string {
+			return Sign(name, spaceID, v)
+		}
+	case SignatureFunc:
+		return func(spaceID string) string {
+			return Sign(name, spaceID, v(spaceID))
+		}
+	}
+	panic("incorrect signature")
+}
+
+func ExtensionSignedKey(ext, signature any) KeyFunc {
+	var desc *ExtensionDescriptor
+
+	switch v := ext.(type) {
+	case Extension:
+		desc = v.GetDescriptor()
+	case *ExtensionDescriptor:
+		desc = v
+	}
+
+	switch v := signature.(type) {
+	case string:
+		return func(spaceID string) string {
+			return Sign(desc.Extension, spaceID, v)
+		}
+	case SignatureFunc:
+		return func(spaceID string) string {
+			return Sign(desc.Extension, spaceID, v(spaceID))
+		}
+	}
+	panic("incorrect signature")
+}
+
+func Sign(vals ...string) string {
+	value := strings.Join(vals, ".")
+	hash := sha512.Sum512([]byte(value))
+	encoded := base64.StdEncoding.EncodeToString(hash[:])
+	return encoded
+}
diff --git a/pkg/extension/mocks/Extension.go b/pkg/extension/mocks/Extension.go
new file mode 100644
index 0000000000000000000000000000000000000000..87710ccff1d98aa5e0922d7e1563caad8c9f21f1
--- /dev/null
+++ b/pkg/extension/mocks/Extension.go
@@ -0,0 +1,126 @@
+// Code generated by mockery v2.14.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	extensions "git.perx.ru/perxis/perxis-go/proto/extensions"
+
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Extension is an autogenerated mock type for the Extension type
+type Extension struct {
+	mock.Mock
+}
+
+// Action provides a mock function with given fields: ctx, in
+func (_m *Extension) Action(ctx context.Context, in *extensions.ActionRequest) (*extensions.ActionResponse, error) {
+	ret := _m.Called(ctx, in)
+
+	var r0 *extensions.ActionResponse
+	if rf, ok := ret.Get(0).(func(context.Context, *extensions.ActionRequest) *extensions.ActionResponse); ok {
+		r0 = rf(ctx, in)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*extensions.ActionResponse)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *extensions.ActionRequest) error); ok {
+		r1 = rf(ctx, in)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Check provides a mock function with given fields: ctx, in
+func (_m *Extension) Check(ctx context.Context, in *extensions.CheckRequest) error {
+	ret := _m.Called(ctx, in)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *extensions.CheckRequest) error); ok {
+		r0 = rf(ctx, in)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// GetDescriptor provides a mock function with given fields:
+func (_m *Extension) GetDescriptor() *extensions.ExtensionDescriptor {
+	ret := _m.Called()
+
+	var r0 *extensions.ExtensionDescriptor
+	if rf, ok := ret.Get(0).(func() *extensions.ExtensionDescriptor); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*extensions.ExtensionDescriptor)
+		}
+	}
+
+	return r0
+}
+
+// Install provides a mock function with given fields: ctx, in
+func (_m *Extension) Install(ctx context.Context, in *extensions.InstallRequest) error {
+	ret := _m.Called(ctx, in)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *extensions.InstallRequest) error); ok {
+		r0 = rf(ctx, in)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Uninstall provides a mock function with given fields: ctx, in
+func (_m *Extension) Uninstall(ctx context.Context, in *extensions.UninstallRequest) error {
+	ret := _m.Called(ctx, in)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *extensions.UninstallRequest) error); ok {
+		r0 = rf(ctx, in)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Update provides a mock function with given fields: ctx, in
+func (_m *Extension) Update(ctx context.Context, in *extensions.UpdateRequest) error {
+	ret := _m.Called(ctx, in)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *extensions.UpdateRequest) error); ok {
+		r0 = rf(ctx, in)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+type mockConstructorTestingTNewExtension interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewExtension creates a new instance of Extension. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewExtension(t mockConstructorTestingTNewExtension) *Extension {
+	mock := &Extension{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/pkg/extension/server.go b/pkg/extension/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..19fcdf55b4d9cf27e878d6e89abee09cabd2352e
--- /dev/null
+++ b/pkg/extension/server.go
@@ -0,0 +1,136 @@
+package extension
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	pb "git.perx.ru/perxis/perxis-go/proto/extensions"
+)
+
+type Server struct {
+	services map[string]Extension
+	pb.UnimplementedExtensionServer
+}
+
+func NewServer(svc ...Extension) *Server {
+	srv := &Server{
+		services: make(map[string]Extension, len(svc)),
+	}
+	for _, s := range svc {
+		srv.services[s.GetDescriptor().Extension] = s
+	}
+	return srv
+}
+
+func getResult(ext string, err error) *RequestResult {
+	res := new(RequestResult)
+	res.Extension = ext
+	res.State = RequestOK
+
+	if err != nil {
+		res.State = RequestError
+		res.Error = err.Error()
+		errs := errors.GetErrors(err)
+		if errs == nil {
+			errs = append(errs, err)
+		}
+		for _, e := range errs {
+			res.Msg += errors.GetDetail(e) + "\n"
+		}
+	}
+	return res
+}
+
+func (srv *Server) getResults(extensions []string, fn func(svc Extension) error) []*RequestResult {
+
+	var results []*RequestResult
+
+	for _, e := range extensions {
+		svc, ok := srv.services[e]
+		if !ok {
+			results = append(results, getResult(e, ErrUnknownExtension))
+			continue
+		}
+
+		err := fn(svc)
+		results = append(results, getResult(e, err))
+	}
+
+	return results
+}
+
+func (srv *Server) Install(ctx context.Context, request *InstallRequest) (*InstallResponse, error) {
+	res := srv.getResults(request.Extensions, func(svc Extension) error { return svc.Install(ctx, request) })
+	return &InstallResponse{Results: res}, nil
+}
+
+func (srv *Server) Check(ctx context.Context, request *CheckRequest) (*CheckResponse, error) {
+	res := srv.getResults(request.Extensions, func(svc Extension) error { return svc.Check(ctx, request) })
+	return &CheckResponse{Results: res}, nil
+}
+
+func (srv *Server) Uninstall(ctx context.Context, request *UninstallRequest) (*UninstallResponse, error) {
+	res := srv.getResults(request.Extensions, func(svc Extension) error { return svc.Uninstall(ctx, request) })
+	return &UninstallResponse{Results: res}, nil
+}
+
+func (srv *Server) Update(ctx context.Context, request *UpdateRequest) (*UpdateResponse, error) {
+	res := srv.getResults(request.Extensions, func(svc Extension) error { return svc.Update(ctx, request) })
+	return &UpdateResponse{Results: res}, nil
+}
+
+func (srv *Server) Action(ctx context.Context, in *ActionRequest) (*ActionResponse, error) {
+
+	svc, ok := srv.services[in.Extension]
+	if !ok {
+		return nil, ErrUnknownExtension
+	}
+
+	out, err := svc.Action(ctx, in)
+
+	if out == nil {
+		out = &ActionResponse{}
+	}
+
+	if err != nil {
+		out.State = ResponseError
+		out.Error = err.Error()
+		out.Msg += errors.GetDetail(err)
+	}
+
+	return out, nil
+}
+
+func (srv *Server) Start() error {
+	var errs []error
+	for _, svc := range srv.services {
+		if r, ok := svc.(Runnable); ok {
+			if err := r.Start(); err != nil {
+				errs = append(errs, err)
+			}
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrStart, errs...)
+	}
+
+	return nil
+}
+
+func (srv *Server) Stop() error {
+	var errs []error
+	for _, svc := range srv.services {
+		if r, ok := svc.(Runnable); ok {
+			if err := r.Stop(); err != nil {
+				errs = append(errs, err)
+			}
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrStop, errs...)
+	}
+
+	return nil
+}
diff --git a/pkg/extension/server_test.go b/pkg/extension/server_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..98d307d198a71541dd779231edc09c5b0467f84c
--- /dev/null
+++ b/pkg/extension/server_test.go
@@ -0,0 +1,90 @@
+package extension
+
+import (
+	"reflect"
+	"strings"
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"git.perx.ru/perxis/perxis-go/pkg/extension/mocks"
+	"github.com/stretchr/testify/mock"
+)
+
+func TestGetResults(t *testing.T) {
+
+	getMockExtension := func(name string, wantErr ...bool) Extension {
+		ext := &mocks.Extension{}
+		ext.On("GetDescriptor").Return(&ExtensionDescriptor{
+			Extension:   name,
+			Title:       strings.ToTitle(name),
+			Description: "test extension",
+			Version:     "0.0.0",
+		})
+
+		var err error
+		if len(wantErr) > 0 {
+			err = errors.WithDetail(errors.New("some err"), "Ошибка")
+		}
+
+		ext.On("Install", mock.Anything, mock.Anything).Return(err)
+		return ext
+	}
+
+	tests := []struct {
+		name       string
+		services   []Extension
+		extensions []string
+		fn         func(svc Extension) error
+		want       []*RequestResult
+	}{
+		{
+			name:       "one extension without errors",
+			services:   []Extension{getMockExtension("a"), getMockExtension("b")},
+			extensions: []string{"a"},
+			fn:         func(svc Extension) error { return nil },
+			want: []*RequestResult{
+				{Extension: "a", State: RequestOK},
+			},
+		},
+		{
+			name:       "multiple extensions without errors",
+			services:   []Extension{getMockExtension("a"), getMockExtension("b"), getMockExtension("c")},
+			extensions: []string{"a", "c"},
+			fn:         func(svc Extension) error { return nil },
+			want: []*RequestResult{
+				{Extension: "a", State: RequestOK},
+				{Extension: "c", State: RequestOK},
+			},
+		},
+		{
+			name:       "multiple extensions, one returns error",
+			services:   []Extension{getMockExtension("a"), getMockExtension("b"), getMockExtension("c", true)},
+			extensions: []string{"a", "c"},
+			fn:         func(svc Extension) error { return svc.Install(nil, nil) },
+			want: []*RequestResult{
+				{Extension: "a", State: RequestOK},
+				{Extension: "c", State: RequestError, Error: "some err", Msg: "Ошибка\n"},
+			},
+		},
+		{
+			name:       "multiple extensions, all return error",
+			services:   []Extension{getMockExtension("a", true), getMockExtension("b", true), getMockExtension("c", true)},
+			extensions: []string{"a", "b", "c"},
+			fn:         func(svc Extension) error { return svc.Install(nil, nil) },
+			want: []*RequestResult{
+				{Extension: "a", State: RequestError, Error: "some err", Msg: "Ошибка\n"},
+				{Extension: "b", State: RequestError, Error: "some err", Msg: "Ошибка\n"},
+				{Extension: "c", State: RequestError, Error: "some err", Msg: "Ошибка\n"},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			srv := NewServer(tt.services...)
+			if got := srv.getResults(tt.extensions, tt.fn); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("getResults() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/setup/client.go b/pkg/setup/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..35f303a1c4734be22144cd26c77f0047348ccb4c
--- /dev/null
+++ b/pkg/setup/client.go
@@ -0,0 +1,199 @@
+package setup
+
+import (
+	"context"
+	"strings"
+
+	"git.perx.ru/perxis/perxis-go/pkg/clients"
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"go.uber.org/zap"
+)
+
+var (
+	ErrCheckClients     = errors.New("clients check error")
+	ErrInstallClients   = errors.New("failed to install clients")
+	ErrUninstallClients = errors.New("failed to uninstall clients")
+)
+
+type ClientsOption func(c *ClientConfig)
+type UpdateClientFn func(s *Setup, exist, new *clients.Client) (*clients.Client, bool)
+type DeleteClientFn func(s *Setup, client *clients.Client) bool
+
+type ClientConfig struct {
+	client   *clients.Client
+	UpdateFn UpdateClientFn
+	DeleteFn DeleteClientFn
+}
+
+func NewClientConfig(client *clients.Client, opt ...ClientsOption) ClientConfig {
+	c := ClientConfig{client: client}
+
+	UpdateExistingClient()(&c)
+	DeleteClientIfRemove()(&c)
+
+	for _, o := range opt {
+		o(&c)
+	}
+
+	return c
+}
+
+func OverwriteClient() ClientsOption {
+	return func(c *ClientConfig) {
+		c.UpdateFn = func(s *Setup, old, new *clients.Client) (*clients.Client, bool) { return new, true }
+	}
+}
+
+func KeepExistingClient() ClientsOption {
+	return func(c *ClientConfig) {
+		c.UpdateFn = func(s *Setup, old, new *clients.Client) (*clients.Client, bool) { return old, false }
+	}
+}
+
+func DeleteClient() ClientsOption {
+	return func(c *ClientConfig) {
+		c.DeleteFn = func(s *Setup, client *clients.Client) bool { return true }
+	}
+}
+
+func DeleteClientIfRemove() ClientsOption {
+	return func(c *ClientConfig) {
+		c.DeleteFn = func(s *Setup, client *clients.Client) bool { return s.IsRemove() }
+	}
+}
+
+func UpdateExistingClient() ClientsOption {
+	return func(c *ClientConfig) {
+		c.UpdateFn = func(s *Setup, exist, client *clients.Client) (*clients.Client, bool) {
+			if exist.Name == "" {
+				exist.Name = client.Name
+			}
+
+			if exist.Description == "" {
+				exist.Description = client.Description
+			}
+
+			if exist.OAuth == nil {
+				exist.OAuth = client.OAuth
+			}
+
+			if exist.TLS == nil {
+				exist.TLS = client.TLS
+			}
+
+			if exist.APIKey == nil {
+				exist.APIKey = client.APIKey
+			}
+
+			exist.Disabled = client.Disabled
+			exist.RoleID = client.RoleID
+
+			return exist, true
+		}
+	}
+}
+
+func (s *Setup) InstallClients(ctx context.Context) error {
+	if len(s.Clients) == 0 {
+		return nil
+	}
+
+	s.logger.Debug("Install clients", zap.String("Space ID", s.SpaceID))
+
+	var errs []error
+	for _, c := range s.Clients {
+		err := s.InstallClient(ctx, c)
+		if err != nil {
+			errs = append(errs, errors.WithDetailf(err, "Возникла ошибка при настройке клиента %s(%s)", c.client.Name, c.client.ID))
+			s.logger.Error("Failed to install client", zap.String("Client ID", c.client.ID), zap.String("Client Name", c.client.Name), zap.Error(err))
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrInstallClients, errs...)
+	}
+
+	return nil
+}
+
+func (s *Setup) InstallClient(ctx context.Context, c ClientConfig) error {
+	client := c.client
+	client.SpaceID = s.SpaceID
+
+	if s.IsForce() {
+		s.content.Clients.Delete(ctx, s.SpaceID, c.client.ID)
+		_, err := s.content.Clients.Create(ctx, c.client)
+		return err
+	}
+
+	exist, err := s.content.Clients.Get(ctx, s.SpaceID, c.client.ID)
+	if err != nil {
+		if !strings.Contains(err.Error(), clients.ErrNotFound.Error()) {
+			return err
+		}
+
+		_, err = s.content.Clients.Create(ctx, c.client)
+		return err
+	}
+
+	if client, upd := c.UpdateFn(s, exist, c.client); upd {
+		return s.content.Clients.Update(ctx, client)
+	}
+
+	return nil
+}
+
+func (s *Setup) CheckClients(ctx context.Context) (err error) {
+	if len(s.Clients) == 0 {
+		return nil
+	}
+
+	var errs []error
+	s.logger.Debug("Check clients", zap.String("Space ID", s.SpaceID))
+	for _, c := range s.Clients {
+		err := s.CheckClient(ctx, c.client)
+		if err != nil {
+			errs = append(errs, errors.WithDetailf(err, "Не найден клиент %s(%s)", c.client.Name, c.client.ID))
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrCheckClients, errs...)
+	}
+
+	return nil
+}
+
+func (s *Setup) CheckClient(ctx context.Context, client *clients.Client) error {
+	_, err := s.content.Clients.Get(ctx, s.SpaceID, client.ID)
+	return err
+}
+
+func (s *Setup) UninstallClients(ctx context.Context) error {
+	if len(s.Clients) == 0 {
+		return nil
+	}
+
+	s.logger.Debug("Uninstall clients", zap.String("Space ID", s.SpaceID))
+
+	var errs []error
+	for _, c := range s.Clients {
+		if err := s.UninstallClient(ctx, c); err != nil {
+			errs = append(errs, errors.WithDetailf(err, "Возникла ошибка при удалении клиента %s(%s)", c.client.Name, c.client.ID))
+			s.logger.Error("Failed to uninstall client", zap.String("Client ID", c.client.ID), zap.String("Client Name", c.client.Name), zap.Error(err))
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrUninstallClients, errs...)
+	}
+
+	return nil
+}
+
+func (s *Setup) UninstallClient(ctx context.Context, c ClientConfig) error {
+	if c.DeleteFn(s, c.client) {
+		return s.content.Clients.Delete(ctx, s.SpaceID, c.client.ID)
+	}
+	return nil
+}
diff --git a/pkg/setup/collection.go b/pkg/setup/collection.go
new file mode 100644
index 0000000000000000000000000000000000000000..176fddc59a959e023e5756d7bd4962aaed6a83fc
--- /dev/null
+++ b/pkg/setup/collection.go
@@ -0,0 +1,225 @@
+package setup
+
+import (
+	"context"
+	"reflect"
+	"strings"
+
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"go.uber.org/zap"
+)
+
+var (
+	ErrCheckCollections     = errors.New("collections check error")
+	ErrInstallCollections   = errors.New("failed to install collections")
+	ErrUninstallCollections = errors.New("failed to uninstall collections")
+)
+
+type CollectionsOption func(c *CollectionConfig)
+type UpdateCollectionFn func(s *Setup, exist, new *collections.Collection) (coll *collections.Collection, upd bool, setSchema bool)
+type DeleteCollectionFn func(s *Setup, col *collections.Collection) bool
+
+type CollectionConfig struct {
+	collection *collections.Collection
+	UpdateFn   UpdateCollectionFn
+	DeleteFn   DeleteCollectionFn
+}
+
+func NewCollectionConfig(collection *collections.Collection, opt ...CollectionsOption) CollectionConfig {
+	c := CollectionConfig{collection: collection}
+
+	UpdateExistingCollection()(&c)
+	DeleteCollectionIfRemove()(&c)
+
+	for _, o := range opt {
+		o(&c)
+	}
+
+	return c
+}
+
+func OverwriteCollection() CollectionsOption {
+	return func(c *CollectionConfig) {
+		c.UpdateFn = func(s *Setup, old, new *collections.Collection) (*collections.Collection, bool, bool) {
+			return new, true, true
+		}
+	}
+}
+
+func KeepExistingCollection() CollectionsOption {
+	return func(c *CollectionConfig) {
+		c.UpdateFn = func(s *Setup, old, new *collections.Collection) (*collections.Collection, bool, bool) {
+			return old, false, false
+		}
+	}
+}
+
+func DeleteCollection() CollectionsOption {
+	return func(c *CollectionConfig) {
+		c.DeleteFn = func(s *Setup, collection *collections.Collection) bool { return true }
+	}
+}
+
+func DeleteCollectionIfRemove() CollectionsOption {
+	return func(c *CollectionConfig) {
+		c.DeleteFn = func(s *Setup, collection *collections.Collection) bool { return s.IsRemove() }
+	}
+}
+
+func UpdateExistingCollection() CollectionsOption {
+	return func(c *CollectionConfig) {
+		c.UpdateFn = func(s *Setup, exist, collection *collections.Collection) (*collections.Collection, bool, bool) {
+			if len(exist.Tags) > 0 {
+				collection.Tags = append(exist.Tags, collection.Tags...)
+			}
+
+			return collection, true, !collection.IsView() && !reflect.DeepEqual(exist.Schema, collection.Schema)
+		}
+	}
+}
+
+func (s *Setup) InstallCollections(ctx context.Context) (err error) {
+	if len(s.Collections) == 0 {
+		return nil
+	}
+
+	s.logger.Debug("Install collections", zap.Int("Collections", len(s.Collections)))
+
+	var errs []error
+	var migrate, setSchema bool
+
+	for _, c := range s.Collections {
+		setSchema, err = s.InstallCollection(ctx, c)
+		if err != nil {
+			errs = append(errs, errors.WithDetailf(err, "Возникла ошибка при настройке коллекции %s(%s)", c.collection.Name, c.collection.ID))
+			s.logger.Error("Failed to install collection",
+				zap.String("Collection ID", c.collection.ID),
+				zap.String("Collection Name", c.collection.Name),
+				zap.Error(err),
+			)
+		}
+		if setSchema {
+			migrate = true
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrInstallCollections, errs...)
+	}
+
+	if migrate {
+		if err = s.content.Environments.Migrate(ctx, s.SpaceID, s.EnvironmentID, &environments.MigrateOptions{Wait: true}); err != nil {
+			s.logger.Error(
+				"Failed to migrate environment",
+				zap.String("Space ID", s.SpaceID),
+				zap.String("Environment ID", s.EnvironmentID),
+				zap.Error(err),
+			)
+
+			return errors.WithErrors(ErrInstallCollections, err)
+		}
+	}
+
+	return nil
+}
+
+func (s *Setup) InstallCollection(ctx context.Context, c CollectionConfig) (setSchema bool, err error) {
+	collection := c.collection
+	collection.SpaceID, collection.EnvID = s.SpaceID, s.EnvironmentID
+
+	var exist *collections.Collection
+	// isForce - не удалять коллекцию, если она уже существует
+	exist, err = s.content.Collections.Get(ctx, collection.SpaceID, collection.EnvID, collection.ID)
+	if err != nil && !strings.Contains(err.Error(), collections.ErrNotFound.Error()) {
+		return false, err
+	}
+
+	if exist == nil {
+		setSchema = !collection.IsView()
+		exist, err = s.content.Collections.Create(ctx, collection)
+		if err != nil {
+			return false, err
+		}
+	} else {
+		var upd bool
+		collection, upd, setSchema = c.UpdateFn(s, exist, c.collection)
+		if upd {
+			if err = s.content.Collections.Update(ctx, collection); err != nil {
+				return false, err
+			}
+		}
+	}
+
+	if setSchema {
+		err = s.content.Collections.SetSchema(ctx, collection.SpaceID, collection.EnvID, collection.ID, collection.Schema)
+		if err != nil {
+			return false, err
+		}
+	}
+
+	return setSchema, nil
+}
+
+func (s *Setup) CheckCollections(ctx context.Context) error {
+	if len(s.Collections) == 0 {
+		return nil
+	}
+
+	s.logger.Debug("Check collections", zap.Int("Collections", len(s.Collections)))
+
+	var errs []error
+	for _, c := range s.Collections {
+		if err := s.CheckCollection(ctx, c); err != nil {
+			errs = append(errs, errors.WithDetailf(err, "Не найдена коллекция %s(%s)", c.collection.ID, c.collection.Name))
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrCheckCollections, errs...)
+	}
+
+	return nil
+}
+
+func (s *Setup) CheckCollection(ctx context.Context, c CollectionConfig) (err error) {
+	_, err = s.content.Collections.Get(ctx, s.SpaceID, s.EnvironmentID, c.collection.ID)
+	return err
+}
+
+func (s *Setup) UninstallCollections(ctx context.Context) error {
+	if len(s.Collections) == 0 {
+		return nil
+	}
+
+	s.logger.Debug("Uninstall collections", zap.Int("Collections", len(s.Collections)))
+
+	var errs []error
+	for _, c := range s.Collections {
+		if err := s.UninstallCollection(ctx, c); err != nil {
+			errs = append(errs, errors.WithDetailf(err, "Возникла ошибка при удалении коллекции %s(%s)", c.collection.Name, c.collection.ID))
+			s.logger.Error("Failed to uninstall collection",
+				zap.String("Collection ID", c.collection.ID),
+				zap.String("Collection Name", c.collection.Name),
+				zap.Error(err),
+			)
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrUninstallCollections, errs...)
+	}
+
+	return nil
+}
+
+func (s *Setup) UninstallCollection(ctx context.Context, c CollectionConfig) error {
+	if c.DeleteFn(s, c.collection) {
+		if err := s.content.Collections.Delete(ctx, s.SpaceID, s.EnvironmentID, c.collection.ID); err != nil && !strings.Contains(err.Error(), collections.ErrNotFound.Error()) {
+			return err
+		}
+		s.removeItems(c.collection.ID) // после удаления коллекции нет смысла удалять ее элементы
+	}
+	return nil
+}
diff --git a/pkg/setup/item.go b/pkg/setup/item.go
new file mode 100644
index 0000000000000000000000000000000000000000..79aa76ac1589f3c5b85e05784b048a4e3436dce7
--- /dev/null
+++ b/pkg/setup/item.go
@@ -0,0 +1,203 @@
+package setup
+
+import (
+	"context"
+	"strings"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	"go.uber.org/zap"
+)
+
+var (
+	ErrCheckItems     = errors.New("items check error")
+	ErrInstallItems   = errors.New("failed to install items")
+	ErrUninstallItems = errors.New("failed to uninstall items")
+	ErrItemsNotFound  = errors.New("item not found")
+)
+
+type ItemsOption func(c *ItemConfig)
+type UpdateItemFn func(s *Setup, exist, new *items.Item) (*items.Item, bool)
+type DeleteItemFn func(s *Setup, col *items.Item) bool
+
+type ItemConfig struct {
+	item     *items.Item
+	UpdateFn UpdateItemFn
+	DeleteFn DeleteItemFn
+}
+
+func NewItemConfig(item *items.Item, opt ...ItemsOption) ItemConfig {
+	c := ItemConfig{item: item}
+
+	KeepExistingItem()(&c)
+	DeleteItemIfRemove()(&c)
+
+	for _, o := range opt {
+		o(&c)
+	}
+
+	return c
+}
+
+func OverwriteItem() ItemsOption {
+	return func(c *ItemConfig) {
+		c.UpdateFn = func(s *Setup, old, new *items.Item) (*items.Item, bool) { return new, true }
+	}
+}
+
+func KeepExistingItem() ItemsOption {
+	return func(c *ItemConfig) {
+		c.UpdateFn = func(s *Setup, old, new *items.Item) (*items.Item, bool) { return old, false }
+	}
+}
+
+func DeleteItem() ItemsOption {
+	return func(c *ItemConfig) {
+		c.DeleteFn = func(s *Setup, item *items.Item) bool { return true }
+	}
+}
+
+func DeleteItemIfRemove() ItemsOption {
+	return func(c *ItemConfig) {
+		c.DeleteFn = func(s *Setup, item *items.Item) bool { return s.IsRemove() }
+	}
+}
+
+func (s *Setup) InstallItems(ctx context.Context) error {
+	if len(s.Items) == 0 {
+		return nil
+	}
+
+	s.logger.Debug("Install items", zap.Int("Items", len(s.Items)))
+
+	var errs []error
+	for _, c := range s.Items {
+		if err := s.InstallItem(ctx, c); err != nil {
+			errs = append(errs, errors.WithDetailf(err, "Возникла ошибка при добавлении элемента %s(%s)", c.item.ID, c.item.CollectionID))
+			s.logger.Error("Failed to install item",
+				zap.String("Item", c.item.ID),
+				zap.String("Item", c.item.CollectionID),
+				zap.Error(err),
+			)
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrInstallItems, errs...)
+	}
+
+	return nil
+}
+
+func (s *Setup) InstallItem(ctx context.Context, c ItemConfig) error {
+	item := c.item
+	item.SpaceID, item.EnvID = s.SpaceID, s.EnvironmentID
+
+	exist, err := s.content.Items.Get(ctx, item.SpaceID, item.EnvID, item.CollectionID, item.ID)
+	if err != nil && !strings.Contains(err.Error(), items.ErrNotFound.Error()) {
+		return err
+	}
+	if exist == nil {
+		return s.CreateAndPublishItem(ctx, item)
+	}
+
+	if item, changed := c.UpdateFn(s, exist, c.item); changed {
+		return s.UpdateAndPublishItem(ctx, item)
+	}
+
+	return nil
+}
+
+func (s *Setup) UninstallItems(ctx context.Context) error {
+	if len(s.Items) == 0 {
+		return nil
+	}
+
+	s.logger.Debug("Uninstall items", zap.Int("Items", len(s.Items)))
+
+	var errs []error
+	for _, c := range s.Items {
+		if err := s.UninstallItem(ctx, c); err != nil {
+			errs = append(errs, errors.WithDetailf(err, "Возникла ошибка при удалении элемента %s(%s)", c.item.ID, c.item.CollectionID))
+			s.logger.Error("Failed to uninstall item",
+				zap.String("Item", c.item.ID),
+				zap.String("Item", c.item.CollectionID),
+				zap.Error(err),
+			)
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrUninstallItems, errs...)
+	}
+
+	return nil
+}
+
+func (s *Setup) UninstallItem(ctx context.Context, c ItemConfig) error {
+	if c.DeleteFn(s, c.item) {
+		err := s.content.Items.Delete(ctx, s.SpaceID, s.EnvironmentID, c.item.CollectionID, c.item.ID)
+		if err != nil && !strings.Contains(err.Error(), items.ErrNotFound.Error()) {
+			return err
+		}
+	}
+	return nil
+}
+
+func (s *Setup) CheckItems(ctx context.Context) error {
+	if len(s.Items) == 0 {
+		return nil
+	}
+
+	var errs []error
+	s.logger.Debug("Check items", zap.Int("Items", len(s.Items)))
+
+	for _, c := range s.Items {
+		if err := s.CheckItem(ctx, c); err != nil {
+			errs = append(errs, errors.WithDetailf(err, "Не найден элемент %s(%s)", c.item.ID, c.item.CollectionID))
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrCheckItems, errs...)
+	}
+
+	return nil
+}
+
+func (s *Setup) CheckItem(ctx context.Context, c ItemConfig) (err error) {
+	_, err = s.content.Items.Get(ctx, s.SpaceID, s.EnvironmentID, c.item.CollectionID, c.item.ID)
+	return err
+}
+
+func (s *Setup) CreateAndPublishItem(ctx context.Context, item *items.Item) error {
+	var err error
+	if item, err = s.content.Items.Create(ctx, item); err != nil {
+		return errors.Wrap(err, "create item")
+	}
+	if err = s.content.Items.Publish(ctx, item); err != nil {
+		return errors.Wrap(err, "publish item")
+	}
+	return nil
+}
+
+func (s *Setup) UpdateAndPublishItem(ctx context.Context, item *items.Item) error {
+	var err error
+	if err = s.content.Items.Update(ctx, item); err != nil {
+		return errors.Wrap(err, "update item")
+	}
+	if err = s.content.Items.Publish(ctx, item); err != nil {
+		return errors.Wrap(err, "publish item")
+	}
+	return nil
+}
+
+func (s *Setup) removeItems(collID string) {
+	itms := make([]ItemConfig, 0, len(s.Items))
+	for _, i := range s.Items {
+		if i.item.CollectionID != collID {
+			itms = append(itms, i)
+		}
+	}
+	s.Items = itms
+}
diff --git a/pkg/setup/role.go b/pkg/setup/role.go
new file mode 100644
index 0000000000000000000000000000000000000000..081696373517eb726a809c3fb509e46525a628c8
--- /dev/null
+++ b/pkg/setup/role.go
@@ -0,0 +1,211 @@
+package setup
+
+import (
+	"context"
+	"strings"
+
+	"git.perx.ru/perxis/perxis-go/pkg/data"
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"git.perx.ru/perxis/perxis-go/pkg/permission"
+	"git.perx.ru/perxis/perxis-go/pkg/roles"
+	"go.uber.org/zap"
+)
+
+var (
+	ErrCheckRoles     = errors.New("role check error")
+	ErrInstallRoles   = errors.New("failed to install role")
+	ErrUninstallRoles = errors.New("failed to uninstall role")
+)
+
+type RolesOption func(c *RoleConfig)
+type UpdateRoleFn func(s *Setup, exist, new *roles.Role) (*roles.Role, bool)
+type DeleteRoleFn func(s *Setup, role *roles.Role) bool
+
+type RoleConfig struct {
+	role     *roles.Role
+	UpdateFn UpdateRoleFn
+	DeleteFn DeleteRoleFn
+}
+
+func NewRoleConfig(role *roles.Role, opt ...RolesOption) RoleConfig {
+	c := RoleConfig{role: role}
+
+	UpdateExistingRole()(&c)
+	DeleteRoleIfRemove()(&c)
+
+	for _, o := range opt {
+		o(&c)
+	}
+	return c
+}
+
+func OverwriteRole() RolesOption {
+	return func(c *RoleConfig) {
+		c.UpdateFn = func(s *Setup, old, new *roles.Role) (*roles.Role, bool) { return new, true }
+	}
+}
+
+//func OverwriteRoleIfChanged() RolesOption {
+//	return func(c *RoleConfig) {
+//		c.UpdateFn = func(s *Setup, old, new *roles.Role) (*roles.Role, bool) {
+//			changed := old.Description != new.Description || old.AllowManagement != new.AllowManagement ||
+//				!data.ElementsMatch(old.Environments, new.Environments)
+//			return new, changed
+//		}
+//	}
+//}
+
+func KeepExistingRole() RolesOption {
+	return func(c *RoleConfig) {
+		c.UpdateFn = func(s *Setup, old, new *roles.Role) (*roles.Role, bool) { return old, false }
+	}
+}
+
+func DeleteRole() RolesOption {
+	return func(c *RoleConfig) {
+		c.DeleteFn = func(s *Setup, role *roles.Role) bool { return true }
+	}
+}
+
+func DeleteRoleIfRemove() RolesOption {
+	return func(c *RoleConfig) {
+		c.DeleteFn = func(s *Setup, role *roles.Role) bool { return s.IsRemove() }
+	}
+}
+
+func UpdateExistingRole() RolesOption {
+	return func(c *RoleConfig) {
+		c.UpdateFn = func(s *Setup, exist, new *roles.Role) (*roles.Role, bool) {
+
+			// если передан флаг force, то обновляем все поля роли на переданные
+			if s.IsForce() {
+				return new, true
+			}
+
+			if exist.Description == "" {
+				exist.Description = new.Description
+			}
+
+			if len(exist.Environments) == 0 {
+				exist.Environments = new.Environments
+			}
+
+			if !data.Contains(s.EnvironmentID, exist.Environments) {
+				exist.Environments = append(exist.Environments, s.EnvironmentID)
+			}
+
+			exist.Rules = permission.MergeRules(exist.Rules, new.Rules)
+
+			if !exist.AllowManagement {
+				exist.AllowManagement = new.AllowManagement
+			}
+
+			return exist, true
+		}
+	}
+}
+
+func (s *Setup) InstallRoles(ctx context.Context) error {
+	if len(s.Roles) == 0 {
+		return nil
+	}
+
+	s.logger.Debug("Install role", zap.String("Space ID", s.SpaceID), zap.Int("Roles", len(s.Roles)))
+
+	var errs []error
+	for _, c := range s.Roles {
+		if err := s.InstallRole(ctx, c); err != nil {
+			errs = append(errs, errors.WithDetailf(err, "Возникла ошибка при настройке роли %s(%s)", c.role.ID, c.role.Description))
+			s.logger.Error("Failed to install role", zap.String("Role ID", c.role.ID), zap.Error(err))
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrInstallRoles, errs...)
+	}
+
+	return nil
+}
+
+func (s *Setup) InstallRole(ctx context.Context, c RoleConfig) error {
+	role := c.role
+	role.SpaceID = s.SpaceID
+
+	if !data.Contains(s.EnvironmentID, c.role.Environments) {
+		role.Environments = append(role.Environments, s.EnvironmentID)
+	}
+
+	exist, err := s.content.Roles.Get(ctx, s.SpaceID, role.ID)
+	if err != nil {
+		if !strings.Contains(err.Error(), roles.ErrNotFound.Error()) {
+			return err
+		}
+
+		_, err = s.content.Roles.Create(ctx, role)
+		return err
+	}
+
+	if r, upd := c.UpdateFn(s, exist, role); upd {
+		return s.content.Roles.Update(ctx, r)
+	}
+
+	return nil
+}
+
+func (s *Setup) UninstallRoles(ctx context.Context) error {
+	if len(s.Roles) == 0 {
+		return nil
+	}
+
+	s.logger.Debug("Uninstall role", zap.String("Space ID", s.SpaceID), zap.Int("Roles", len(s.Roles)))
+
+	var errs []error
+	for _, c := range s.Roles {
+		if err := s.UninstallRole(ctx, c); err != nil {
+			errs = append(errs, errors.WithDetailf(err, "Возникла ошибка при удалении роли %s(%s)", c.role.ID, c.role.Description))
+			s.logger.Error("Failed to uninstall role", zap.String("Role ID", c.role.ID), zap.Error(err))
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrUninstallRoles, errs...)
+	}
+
+	return nil
+}
+
+func (s *Setup) UninstallRole(ctx context.Context, c RoleConfig) error {
+	if c.DeleteFn(s, c.role) {
+		err := s.content.Roles.Delete(ctx, s.SpaceID, c.role.ID)
+		if err != nil && !strings.Contains(err.Error(), roles.ErrNotFound.Error()) {
+			return err
+		}
+	}
+	return nil
+}
+
+func (s *Setup) CheckRoles(ctx context.Context) error {
+	if len(s.Roles) == 0 {
+		return nil
+	}
+
+	s.logger.Debug("Check role", zap.String("Space ID", s.SpaceID))
+
+	var errs []error
+	for _, c := range s.Roles {
+		if err := s.CheckRole(ctx, c.role); err != nil {
+			errs = append(errs, errors.WithDetailf(err, "Не найдена роль %s(%s)", c.role.ID, c.role.Description))
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.WithErrors(ErrCheckRoles, errs...)
+	}
+
+	return nil
+}
+
+func (s *Setup) CheckRole(ctx context.Context, role *roles.Role) error {
+	_, err := s.content.Roles.Get(ctx, s.SpaceID, role.ID)
+	return err
+}
diff --git a/pkg/setup/setup.go b/pkg/setup/setup.go
new file mode 100644
index 0000000000000000000000000000000000000000..8b7097a282d0ec73462ae8175a28f8013f4207f0
--- /dev/null
+++ b/pkg/setup/setup.go
@@ -0,0 +1,184 @@
+package setup
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/clients"
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/content"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	"git.perx.ru/perxis/perxis-go/pkg/roles"
+	"go.uber.org/zap"
+)
+
+// Setup реализует процесс настройки пространства. Указав необходимые требования к конфигурации пространства можно
+// выполнить процесс установки, проверки и удаления требований.
+type Setup struct {
+	SpaceID       string
+	EnvironmentID string
+
+	Roles       []RoleConfig
+	Clients     []ClientConfig
+	Collections []CollectionConfig
+	Items       []ItemConfig
+
+	content *content.Content
+
+	force  bool
+	remove bool
+
+	errors []error
+	logger *zap.Logger
+}
+
+func NewSetup(content *content.Content, spaceID, environmentID string, logger *zap.Logger) *Setup {
+	//logger = logger.With(zap.String("Space", spaceID), zap.String("Environment", environmentID))
+
+	return &Setup{
+		SpaceID:       spaceID,
+		EnvironmentID: environmentID,
+		content:       content,
+		logger:        logger,
+	}
+}
+
+func (s *Setup) WithForce(force bool) *Setup {
+	setup := *s
+	setup.force = force
+	return &setup
+}
+
+func (s *Setup) IsForce() bool {
+	return s.force
+}
+
+func (s *Setup) WithRemove(remove bool) *Setup {
+	setup := *s
+	setup.remove = remove
+	return &setup
+}
+
+func (s *Setup) IsRemove() bool {
+	return s.remove
+}
+
+func (s *Setup) HasErrors() bool {
+	return len(s.errors) > 0
+}
+
+func (s *Setup) AddError(err error) {
+	s.errors = append(s.errors, err)
+}
+
+// AddRoles добавляет требования к настройке ролей в пространстве
+func (s *Setup) AddRoles(roles []*roles.Role, opt ...RolesOption) *Setup {
+	for _, role := range roles {
+		s.AddRole(role, opt...)
+	}
+	return s
+}
+
+func (s *Setup) AddRole(role *roles.Role, opt ...RolesOption) *Setup {
+	s.Roles = append(s.Roles, NewRoleConfig(role, opt...))
+	return s
+}
+
+// AddClients добавляет требования к настройке приложений в пространстве
+func (s *Setup) AddClients(clients []*clients.Client, opt ...ClientsOption) *Setup {
+	for _, client := range clients {
+		s.AddClient(client, opt...)
+	}
+	return s
+}
+
+func (s *Setup) AddClient(client *clients.Client, opt ...ClientsOption) *Setup {
+	s.Clients = append(s.Clients, NewClientConfig(client, opt...))
+	return s
+}
+
+// AddCollections добавляет требования к настройке коллекций в пространстве
+func (s *Setup) AddCollections(collections []*collections.Collection, opt ...CollectionsOption) *Setup {
+	for _, col := range collections {
+		s.AddCollection(col, opt...)
+	}
+	return s
+}
+
+func (s *Setup) AddCollection(collection *collections.Collection, opt ...CollectionsOption) *Setup {
+	s.Collections = append(s.Collections, NewCollectionConfig(collection, opt...))
+	return s
+}
+
+// AddItems добавляет требования к настройке элементов в пространстве
+func (s *Setup) AddItems(items []*items.Item, opt ...ItemsOption) *Setup {
+	for _, item := range items {
+		s.AddItem(item, opt...)
+	}
+	return s
+}
+
+func (s *Setup) AddItem(item *items.Item, opt ...ItemsOption) *Setup {
+	s.Items = append(s.Items, NewItemConfig(item, opt...))
+	return s
+}
+
+// Install выполняет установку необходимых требований
+func (s *Setup) Install(ctx context.Context) error {
+	s.logger = s.logger.With(zap.String("Space", s.SpaceID), zap.String("Environment", s.EnvironmentID))
+
+	if err := s.InstallRoles(ctx); err != nil {
+		return err
+	}
+	if err := s.InstallClients(ctx); err != nil {
+		return err
+	}
+	if err := s.InstallCollections(ctx); err != nil {
+		return err
+	}
+	if err := s.InstallItems(ctx); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Check выполняет проверку требований
+func (s *Setup) Check(ctx context.Context) error {
+	s.logger = s.logger.With(zap.String("Space", s.SpaceID), zap.String("Environment", s.EnvironmentID))
+
+	if err := s.CheckRoles(ctx); err != nil {
+		return err
+	}
+	if err := s.CheckClients(ctx); err != nil {
+		return err
+	}
+	if err := s.CheckCollections(ctx); err != nil {
+		return err
+	}
+	if err := s.CheckItems(ctx); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Uninstall выполняет удаление установленных раннее требований
+func (s *Setup) Uninstall(ctx context.Context) error {
+	s.logger = s.logger.With(zap.String("Space", s.SpaceID), zap.String("Environment", s.EnvironmentID))
+
+	// В случае если необходимо удалить данные удаляем все что создано при установке расширения
+	if err := s.UninstallClients(ctx); err != nil {
+		return err
+	}
+	if err := s.UninstallRoles(ctx); err != nil {
+		return err
+	}
+	if err := s.UninstallCollections(ctx); err != nil {
+		return err
+	}
+	if err := s.UninstallItems(ctx); err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/pkg/setup/setup_test.go b/pkg/setup/setup_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d3c3c363e9ec048b53704829413238b84528a24c
--- /dev/null
+++ b/pkg/setup/setup_test.go
@@ -0,0 +1,1210 @@
+package setup
+
+import (
+	"context"
+	"errors"
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/pkg/clients"
+	clientsMock "git.perx.ru/perxis/perxis-go/pkg/clients/mocks"
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	collectionMock "git.perx.ru/perxis/perxis-go/pkg/collections/mocks"
+	"git.perx.ru/perxis/perxis-go/pkg/content"
+	"git.perx.ru/perxis/perxis-go/pkg/data"
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+	environmentMock "git.perx.ru/perxis/perxis-go/pkg/environments/mocks"
+	"git.perx.ru/perxis/perxis-go/pkg/extension"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	itemsMock "git.perx.ru/perxis/perxis-go/pkg/items/mocks"
+	"git.perx.ru/perxis/perxis-go/pkg/roles"
+	rolesMock "git.perx.ru/perxis/perxis-go/pkg/roles/mocks"
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zaptest"
+)
+
+const (
+	spaceID = "sp"
+	envID   = "env"
+)
+
+func getCollections() []*collections.Collection {
+	return []*collections.Collection{
+		{
+			ID:      "coll1",
+			SpaceID: spaceID,
+			EnvID:   envID,
+			Schema:  schema.New(),
+			Name:    "Коллекция",
+		},
+	}
+}
+
+func getRoles() []*roles.Role {
+	return []*roles.Role{
+		{
+			ID:      "role",
+			SpaceID: spaceID,
+		},
+	}
+}
+
+func getClients() []*clients.Client {
+	return []*clients.Client{
+		{
+			ID:      "client",
+			SpaceID: spaceID,
+			RoleID:  "role",
+		},
+	}
+}
+
+func getActions() []*items.Item {
+	return []*items.Item{
+		{
+			ID:           "act",
+			SpaceID:      spaceID,
+			EnvID:        envID,
+			CollectionID: extension.ActionsCollectionID,
+			Data: map[string]interface{}{
+				"action":    "act",
+				"name":      "Action",
+				"extension": "ext",
+			},
+		},
+	}
+}
+
+func newSetup(content *content.Content, t *testing.T) *Setup {
+	logger := zaptest.NewLogger(t, zaptest.WrapOptions())
+
+	setup := NewSetup(content, spaceID, envID, logger)
+	setup.AddCollections(getCollections())
+	setup.AddRoles(getRoles())
+	setup.AddClients(getClients())
+	setup.AddItems(getActions(), OverwriteItem())
+
+	return setup
+}
+
+func TestSetupInstall(t *testing.T) {
+	t.Run("Success, nothing to install", func(t *testing.T) {
+		logger := zaptest.NewLogger(t, zaptest.WrapOptions())
+
+		setup := NewSetup(nil, spaceID, envID, logger)
+
+		err := setup.Install(context.Background())
+
+		require.NoError(t, err)
+	})
+
+	t.Run("Success, no force", func(t *testing.T) {
+		envMocks := &environmentMock.Environments{}
+		envMocks.On("Migrate", mock.Anything, spaceID, envID, &environments.MigrateOptions{Wait: true}).
+			Return(nil).
+			Once()
+
+		collsMock := &collectionMock.Collections{}
+		for _, collection := range getCollections() {
+			collsMock.On("Get", mock.Anything, spaceID, envID, collection.ID).
+				Return(nil, collections.ErrNotFound).
+				Once()
+
+			collsMock.On("Create", mock.Anything, collection).
+				Return(collection, nil).
+				Once()
+
+			collsMock.On("SetSchema", mock.Anything, spaceID, envID, collection.ID, collection.Schema).
+				Return(nil).
+				Once()
+		}
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).
+				Return(nil, roles.ErrNotFound).
+				Once()
+
+			rMock.On("Create", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
+				create := args[1].(*roles.Role)
+				require.True(t, data.Contains(envID, create.Environments))
+			}).Return(role, nil).Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Get", mock.Anything, spaceID, client.ID).
+				Return(nil, clients.ErrNotFound).
+				Once()
+
+			clMock.On("Create", mock.Anything, client).
+				Return(client, nil).
+				Once()
+		}
+
+		itmMock := &itemsMock.Items{}
+		for _, act := range getActions() {
+			itmMock.On("Get", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
+				Return(nil, items.ErrNotFound).
+				Once()
+
+			itmMock.On("Create", mock.Anything, act).
+				Return(act, nil).
+				Once()
+			itmMock.On("Publish", mock.Anything, act).
+				Return(nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Collections:  collsMock,
+			Clients:      clMock,
+			Roles:        rMock,
+			Items:        itmMock,
+			Environments: envMocks,
+		}, t)
+
+		err := setup.Install(context.Background())
+
+		require.NoError(t, err)
+		require.False(t, setup.HasErrors())
+
+		rMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+		itmMock.AssertExpectations(t)
+		envMocks.AssertExpectations(t)
+	})
+
+	t.Run("Success, update existing records", func(t *testing.T) {
+		envMocks := &environmentMock.Environments{}
+
+		collsMock := &collectionMock.Collections{}
+		for _, collection := range getCollections() {
+			collsMock.On("Get", mock.Anything, spaceID, envID, collection.ID).
+				Return(collection, nil).
+				Once()
+
+			collsMock.On("Update", mock.Anything, collection).
+				Return(nil).
+				Once()
+		}
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).
+				Return(role, nil).
+				Once()
+
+			rMock.On("Update", mock.Anything, role).
+				Return(nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Get", mock.Anything, spaceID, client.ID).
+				Return(client, nil).
+				Once()
+
+			clMock.On("Update", mock.Anything, client).
+				Return(nil).
+				Once()
+		}
+
+		itmMock := &itemsMock.Items{}
+		for _, act := range getActions() {
+			itmMock.On("Get", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
+				Return(act, nil).
+				Once()
+			itmMock.On("Update", mock.Anything, mock.Anything).
+				Return(nil).
+				Once()
+			itmMock.On("Publish", mock.Anything, mock.Anything).
+				Return(nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Collections:  collsMock,
+			Clients:      clMock,
+			Roles:        rMock,
+			Items:        itmMock,
+			Environments: envMocks,
+		}, t)
+
+		err := setup.Install(context.Background())
+
+		require.NoError(t, err)
+		require.False(t, setup.HasErrors())
+
+		rMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+		itmMock.AssertExpectations(t)
+		envMocks.AssertExpectations(t)
+	})
+
+	t.Run("Success, with force", func(t *testing.T) {
+		collsMock := &collectionMock.Collections{}
+		for _, collection := range getCollections() {
+			collsMock.On("Get", mock.Anything, spaceID, envID, collection.ID).
+				Return(collection, nil).
+				Once()
+
+			collsMock.On("Update", mock.Anything, collection).
+				Return(nil).
+				Once()
+		}
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).Return(role, nil).Once()
+			rMock.On("Update", mock.Anything, &roles.Role{ID: "role", SpaceID: "sp", Environments: []string{"env"}}).Return(nil).Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Delete", mock.Anything, spaceID, client.ID).Return(nil).Once()
+
+			clMock.On("Create", mock.Anything, client).
+				Return(client, nil).
+				Once()
+		}
+
+		itmMock := &itemsMock.Items{}
+		for _, act := range getActions() {
+			itmMock.On("Get", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
+				Return(nil, items.ErrNotFound).
+				Once()
+
+			itmMock.On("Create", mock.Anything, act).
+				Return(act, nil).
+				Once()
+			itmMock.On("Publish", mock.Anything, act).
+				Return(nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Collections: collsMock,
+			Clients:     clMock,
+			Roles:       rMock,
+			Items:       itmMock,
+		}, t)
+		setup = setup.WithForce(true)
+
+		err := setup.Install(context.Background())
+
+		require.NoError(t, err)
+		require.False(t, setup.HasErrors())
+
+		rMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+		itmMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't install role, storage returns error", func(t *testing.T) {
+		rMock := &rolesMock.Roles{}
+		rMock.On("Get", mock.Anything, spaceID, mock.Anything).
+			Return(nil, errors.New("can't get role")).
+			Once()
+
+		setup := newSetup(&content.Content{
+			Roles: rMock,
+		}, t)
+
+		err := setup.Install(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "failed to install role")
+
+		rMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't install client, storage returns error", func(t *testing.T) {
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).
+				Return(nil, roles.ErrNotFound).
+				Once()
+
+			rMock.On("Create", mock.Anything, mock.Anything).
+				Return(role, nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Get", mock.Anything, spaceID, client.ID).
+				Return(nil, errors.New("can't get client")).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Clients: clMock,
+			Roles:   rMock,
+		}, t)
+
+		err := setup.Install(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "failed to install clients")
+
+		rMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't get collection, storage returns error", func(t *testing.T) {
+		collsMock := &collectionMock.Collections{}
+		for _, collection := range getCollections() {
+			collsMock.On("Get", mock.Anything, spaceID, envID, collection.ID).
+				Return(nil, errors.New("can't get collection")).
+				Once()
+		}
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).
+				Return(nil, roles.ErrNotFound).
+				Once()
+
+			rMock.On("Create", mock.Anything, mock.Anything).
+				Return(role, nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Get", mock.Anything, spaceID, client.ID).
+				Return(nil, clients.ErrNotFound).
+				Once()
+
+			clMock.On("Create", mock.Anything, client).
+				Return(client, nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Collections: collsMock,
+			Clients:     clMock,
+			Roles:       rMock,
+		}, t)
+
+		err := setup.Install(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "failed to install collections")
+
+		rMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't create collection, storage returns error", func(t *testing.T) {
+		collsMock := &collectionMock.Collections{}
+		for _, collection := range getCollections() {
+			collsMock.On("Get", mock.Anything, spaceID, envID, collection.ID).
+				Return(nil, collections.ErrNotFound).
+				Once()
+
+			collsMock.On("Create", mock.Anything, collection).
+				Return(nil, errors.New("can't create collection")).
+				Once()
+		}
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).
+				Return(nil, roles.ErrNotFound).
+				Once()
+
+			rMock.On("Create", mock.Anything, mock.Anything).
+				Return(role, nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Get", mock.Anything, spaceID, client.ID).
+				Return(nil, clients.ErrNotFound).
+				Once()
+
+			clMock.On("Create", mock.Anything, client).
+				Return(client, nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Collections: collsMock,
+			Clients:     clMock,
+			Roles:       rMock,
+		}, t)
+
+		err := setup.Install(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "failed to install collections")
+
+		rMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't update collection, storage returns error", func(t *testing.T) {
+		collsMock := &collectionMock.Collections{}
+		for _, collection := range getCollections() {
+			collsMock.On("Get", mock.Anything, spaceID, envID, collection.ID).
+				Return(collection, nil).
+				Once()
+
+			collsMock.On("Update", mock.Anything, collection).
+				Return(errors.New("can't update collection")).
+				Once()
+		}
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).
+				Return(nil, roles.ErrNotFound).
+				Once()
+
+			rMock.On("Create", mock.Anything, mock.Anything).
+				Return(role, nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Get", mock.Anything, spaceID, client.ID).
+				Return(nil, clients.ErrNotFound).
+				Once()
+
+			clMock.On("Create", mock.Anything, client).
+				Return(client, nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Collections: collsMock,
+			Clients:     clMock,
+			Roles:       rMock,
+		}, t)
+
+		err := setup.Install(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "failed to install collections")
+
+		rMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't set schema, storage returns error", func(t *testing.T) {
+		collsMock := &collectionMock.Collections{}
+		for _, collection := range getCollections() {
+			collsMock.On("Get", mock.Anything, spaceID, envID, collection.ID).
+				Return(nil, collections.ErrNotFound).
+				Once()
+
+			collsMock.On("Create", mock.Anything, collection).
+				Return(collection, nil).
+				Once()
+
+			collsMock.On("SetSchema", mock.Anything, spaceID, envID, collection.ID, collection.Schema).
+				Return(errors.New("can't set schema")).
+				Once()
+		}
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).
+				Return(nil, roles.ErrNotFound).
+				Once()
+
+			rMock.On("Create", mock.Anything, mock.Anything).
+				Return(role, nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Get", mock.Anything, spaceID, client.ID).
+				Return(nil, clients.ErrNotFound).
+				Once()
+
+			clMock.On("Create", mock.Anything, client).
+				Return(client, nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Collections: collsMock,
+			Clients:     clMock,
+			Roles:       rMock,
+		}, t)
+
+		err := setup.Install(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "failed to install collections")
+
+		rMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't migrate, storage returns error", func(t *testing.T) {
+		envMocks := &environmentMock.Environments{}
+		envMocks.On("Migrate", mock.Anything, spaceID, envID, &environments.MigrateOptions{Wait: true}).
+			Return(errors.New("can't migrate")).
+			Once()
+
+		collsMock := &collectionMock.Collections{}
+		for _, collection := range getCollections() {
+			collsMock.On("Get", mock.Anything, spaceID, envID, collection.ID).
+				Return(nil, collections.ErrNotFound).
+				Once()
+
+			collsMock.On("Create", mock.Anything, collection).
+				Return(collection, nil).
+				Once()
+
+			collsMock.On("SetSchema", mock.Anything, spaceID, envID, collection.ID, collection.Schema).
+				Return(nil).
+				Once()
+		}
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).
+				Return(nil, roles.ErrNotFound).
+				Once()
+
+			rMock.On("Create", mock.Anything, mock.Anything).
+				Return(role, nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Get", mock.Anything, spaceID, client.ID).
+				Return(nil, clients.ErrNotFound).
+				Once()
+
+			clMock.On("Create", mock.Anything, client).
+				Return(client, nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Collections:  collsMock,
+			Clients:      clMock,
+			Roles:        rMock,
+			Environments: envMocks,
+		}, t)
+
+		err := setup.Install(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "failed to install collections")
+
+		rMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't find action, storage returns error", func(t *testing.T) {
+		envMocks := &environmentMock.Environments{}
+		envMocks.On("Migrate", mock.Anything, spaceID, envID, &environments.MigrateOptions{Wait: true}).
+			Return(nil).
+			Once()
+
+		collsMock := &collectionMock.Collections{}
+		for _, collection := range getCollections() {
+			collsMock.On("Get", mock.Anything, spaceID, envID, collection.ID).
+				Return(nil, collections.ErrNotFound).
+				Once()
+
+			collsMock.On("Create", mock.Anything, collection).
+				Return(collection, nil).
+				Once()
+
+			collsMock.On("SetSchema", mock.Anything, spaceID, envID, collection.ID, collection.Schema).
+				Return(nil).
+				Once()
+		}
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).Return(role, nil).Once()
+			rMock.On("Update", mock.Anything, &roles.Role{ID: "role", SpaceID: "sp", Environments: []string{"env"}}).Return(nil).Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Delete", mock.Anything, spaceID, client.ID).Return(nil).Once()
+
+			clMock.On("Create", mock.Anything, client).
+				Return(client, nil).
+				Once()
+		}
+
+		itmMock := &itemsMock.Items{}
+		itmMock.On(
+			"Get", mock.Anything, spaceID, envID, extension.ActionsCollectionID, mock.Anything,
+		).
+			Return(nil, items.ErrNotFound).
+			Once()
+
+		itmMock.On("Create", mock.Anything, mock.Anything).
+			Return(nil, errors.New("can't create item")).
+			Once()
+
+		setup := newSetup(&content.Content{
+			Collections:  collsMock,
+			Clients:      clMock,
+			Roles:        rMock,
+			Items:        itmMock,
+			Environments: envMocks,
+		}, t)
+		setup = setup.WithForce(true)
+
+		err := setup.Install(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "failed to install items")
+
+		rMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+		itmMock.AssertExpectations(t)
+		envMocks.AssertExpectations(t)
+	})
+
+	//t.Run("Can't find task configs, storage returns error", func(t *testing.T) {
+	//	envMocks := &environmentMock.Environments{}
+	//	envMocks.On("Migrate", mock.Anything, spaceID, envID).
+	//		Return(nil).
+	//		Once()
+	//
+	//	collsMock := &collectionMock.Collections{}
+	//	for _, collection := range getCollections() {
+	//		collsMock.On("Get", mock.Anything, spaceID, envID, collection.ID).
+	//			Return(nil, collections.ErrNotFound).
+	//			Once()
+	//
+	//		collsMock.On("Create", mock.Anything, collection).
+	//			Return(collection, nil).
+	//			Once()
+	//
+	//		collsMock.On("SetSchema", mock.Anything, spaceID, envID, collection.ID, collection.Schema).
+	//			Return(nil).
+	//			Once()
+	//	}
+	//
+	//	rMock := &rolesMock.Roles{}
+	//	for _, role := range getRoles() {
+	//		rMock.On("Get", mock.Anything, spaceID, role.ID).
+	//			Return(nil, roles.ErrNotFound).
+	//			Once()
+	//
+	//		rMock.On("Create", mock.Anything, mock.Anything).
+	//			Return(role, nil).
+	//			Once()
+	//	}
+	//
+	//	clMock := &clientsMock.Clients{}
+	//	for _, client := range getClients() {
+	//		clMock.On("Get", mock.Anything, spaceID, client.ID).
+	//			Return(nil, clients.ErrNotFound).
+	//			Once()
+	//
+	//		clMock.On("Create", mock.Anything, client).
+	//			Return(client, nil).
+	//			Once()
+	//	}
+	//
+	//	itmMock := &itemsMock.Items{}
+	//	for _, act := range getActions() {
+	//		itmMock.On("Get", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
+	//			Return(nil, items.ErrNotFound).
+	//			Once()
+	//
+	//		itmMock.On("Create", mock.Anything, act).
+	//			Return(act, nil).
+	//			Once()
+	//		itmMock.On("Publish", mock.Anything, act).
+	//			Return(nil).
+	//			Once()
+	//	}
+	//
+	//	setup := newSetup(&content.Content{
+	//		Collections:  collsMock,
+	//		Clients:      clMock,
+	//		Roles:        rMock,
+	//		Items:        itmMock,
+	//		Environments: envMocks,
+	//	}, t)
+	//
+	//	err := setup.Install(context.Background())
+	//
+	//	require.Error(t, err)
+	//	assert.ErrorContains(t, err, "failed to install task configs")
+	//
+	//	rMock.AssertExpectations(t)
+	//	collsMock.AssertExpectations(t)
+	//	clMock.AssertExpectations(t)
+	//	itmMock.AssertExpectations(t)
+	//	envMocks.AssertExpectations(t)
+	//})
+}
+
+func TestSetupUninstall(t *testing.T) {
+	t.Run("Success, nothing to uninstall", func(t *testing.T) {
+		logger := zaptest.NewLogger(t, zaptest.WrapOptions())
+
+		setup := NewSetup(nil, spaceID, envID, logger)
+
+		err := setup.Uninstall(context.Background())
+
+		require.NoError(t, err)
+	})
+
+	t.Run("Remove", func(t *testing.T) {
+		collsMock := &collectionMock.Collections{}
+		for _, collection := range getCollections() {
+			collsMock.On("Delete", mock.Anything, spaceID, envID, collection.ID).
+				Return(nil).
+				Once()
+		}
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Delete", mock.Anything, spaceID, role.ID).
+				Return(nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Delete", mock.Anything, spaceID, client.ID).Return(nil).Once()
+		}
+
+		itmMock := &itemsMock.Items{}
+		for _, act := range getActions() {
+			itmMock.On("Delete", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
+				Return(nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Collections: collsMock,
+			Clients:     clMock,
+			Roles:       rMock,
+			Items:       itmMock,
+		}, t)
+
+		setup = setup.WithRemove(true)
+		err := setup.Uninstall(context.Background())
+
+		require.NoError(t, err)
+		require.False(t, setup.HasErrors())
+
+		rMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+		itmMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't uninstall clients, storage returns error", func(t *testing.T) {
+		rMock := &rolesMock.Roles{}
+
+		clMock := &clientsMock.Clients{}
+		clMock.On("Delete", mock.Anything, spaceID, mock.Anything).
+			Return(errors.New("can't delete client")).
+			Once()
+
+		itmMock := &itemsMock.Items{}
+		for _, act := range getActions() {
+			itmMock.On("Delete", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
+				Return(nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Clients: clMock,
+			Roles:   rMock,
+			Items:   itmMock,
+		}, t)
+
+		setup = setup.WithRemove(true)
+		err := setup.Uninstall(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "failed to uninstall clients")
+
+		rMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't uninstall role, storage returns error", func(t *testing.T) {
+		rMock := &rolesMock.Roles{}
+		rMock.On("Delete", mock.Anything, spaceID, mock.Anything).
+			Return(errors.New("can't delete role")).
+			Once()
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Delete", mock.Anything, spaceID, client.ID).Return(nil).Once()
+		}
+
+		itmMock := &itemsMock.Items{}
+		for _, act := range getActions() {
+			itmMock.On("Delete", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
+				Return(nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Roles:   rMock,
+			Clients: clMock,
+			Items:   itmMock,
+		}, t)
+
+		setup = setup.WithRemove(true)
+		err := setup.Uninstall(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "failed to uninstall role")
+
+		rMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't uninstall collections, storage returns error", func(t *testing.T) {
+		collsMock := &collectionMock.Collections{}
+		collsMock.On("Delete", mock.Anything, spaceID, envID, mock.Anything).
+			Return(errors.New("can't delete collection")).
+			Once()
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Delete", mock.Anything, spaceID, role.ID).
+				Return(nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Delete", mock.Anything, spaceID, client.ID).Return(nil).Once()
+		}
+
+		itmMock := &itemsMock.Items{}
+		for _, act := range getActions() {
+			itmMock.On("Delete", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
+				Return(nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Collections: collsMock,
+			Clients:     clMock,
+			Roles:       rMock,
+			Items:       itmMock,
+		}, t)
+
+		setup = setup.WithRemove(true)
+		err := setup.Uninstall(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "failed to uninstall collections")
+
+		rMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't uninstall actions, storage returns error", func(t *testing.T) {
+		collsMock := &collectionMock.Collections{}
+		collsMock.On("Delete", mock.Anything, spaceID, envID, mock.Anything).
+			Return(nil).
+			Once()
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Delete", mock.Anything, spaceID, role.ID).
+				Return(nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Delete", mock.Anything, spaceID, client.ID).Return(nil).Once()
+		}
+
+		itmMock := &itemsMock.Items{}
+		for _, act := range getActions() {
+			itmMock.On("Delete", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
+				Return(errors.New("can't delete item")).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Collections: collsMock,
+			Clients:     clMock,
+			Roles:       rMock,
+			Items:       itmMock,
+		}, t)
+
+		setup = setup.WithRemove(true)
+		err := setup.Uninstall(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "failed to uninstall items")
+
+		itmMock.AssertExpectations(t)
+	})
+}
+
+func TestSetupCheck(t *testing.T) {
+	t.Run("Success, nothing to check", func(t *testing.T) {
+		logger := zaptest.NewLogger(t, zaptest.WrapOptions())
+
+		setup := NewSetup(nil, spaceID, envID, logger)
+
+		err := setup.Check(context.Background())
+
+		require.NoError(t, err)
+	})
+
+	t.Run("Success", func(t *testing.T) {
+		collsMock := &collectionMock.Collections{}
+		for _, collection := range getCollections() {
+			collsMock.On("Get", mock.Anything, spaceID, envID, collection.ID).
+				Return(collection, nil).
+				Once()
+		}
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).
+				Return(role, nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Get", mock.Anything, spaceID, client.ID).
+				Return(client, nil).
+				Once()
+		}
+
+		itmMock := &itemsMock.Items{}
+		for _, act := range getActions() {
+			itmMock.On("Get", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
+				Return(act, nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Collections: collsMock,
+			Clients:     clMock,
+			Roles:       rMock,
+			Items:       itmMock,
+		}, t)
+
+		err := setup.Check(context.Background())
+
+		require.NoError(t, err)
+		require.False(t, setup.HasErrors())
+
+		rMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+		itmMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't get role, storage returns error", func(t *testing.T) {
+		rMock := &rolesMock.Roles{}
+		rMock.On("Get", mock.Anything, spaceID, mock.Anything).
+			Return(nil, errors.New("can't get role")).
+			Once()
+
+		setup := newSetup(&content.Content{
+			Roles: rMock,
+		}, t)
+
+		err := setup.Check(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "role check error")
+
+		rMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't get client, storage returns error", func(t *testing.T) {
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).
+				Return(role, nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		clMock.On("Get", mock.Anything, spaceID, mock.Anything).
+			Return(nil, errors.New("can't get client")).
+			Once()
+
+		setup := newSetup(&content.Content{
+			Roles:   rMock,
+			Clients: clMock,
+		}, t)
+
+		err := setup.Check(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "clients check error")
+
+		rMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't get collection, storage returns error", func(t *testing.T) {
+		collsMock := &collectionMock.Collections{}
+		collsMock.On("Get", mock.Anything, spaceID, envID, mock.Anything).
+			Return(nil, errors.New("can't get collection")).
+			Once()
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).
+				Return(role, nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Get", mock.Anything, spaceID, client.ID).
+				Return(client, nil).
+				Once()
+		}
+
+		setup := newSetup(&content.Content{
+			Roles:       rMock,
+			Clients:     clMock,
+			Collections: collsMock,
+		}, t)
+
+		err := setup.Check(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "collections check error")
+
+		rMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+	})
+
+	t.Run("Can't get action, storage returns error", func(t *testing.T) {
+		collsMock := &collectionMock.Collections{}
+		for _, collection := range getCollections() {
+			collsMock.On("Get", mock.Anything, spaceID, envID, collection.ID).
+				Return(collection, nil).
+				Once()
+		}
+
+		rMock := &rolesMock.Roles{}
+		for _, role := range getRoles() {
+			rMock.On("Get", mock.Anything, spaceID, role.ID).
+				Return(role, nil).
+				Once()
+		}
+
+		clMock := &clientsMock.Clients{}
+		for _, client := range getClients() {
+			clMock.On("Get", mock.Anything, spaceID, client.ID).
+				Return(client, nil).
+				Once()
+		}
+
+		itmMock := &itemsMock.Items{}
+		itmMock.On(
+			"Get", mock.Anything, spaceID, envID, extension.ActionsCollectionID, mock.Anything,
+		).
+			Return(nil, errors.New("can't find item")).
+			Once()
+
+		setup := newSetup(&content.Content{
+			Roles:       rMock,
+			Clients:     clMock,
+			Collections: collsMock,
+			Items:       itmMock,
+		}, t)
+
+		err := setup.Check(context.Background())
+
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "items check error")
+
+		rMock.AssertExpectations(t)
+		clMock.AssertExpectations(t)
+		collsMock.AssertExpectations(t)
+	})
+
+	//t.Run("Can't get task config, storage returns error", func(t *testing.T) {
+	//	collsMock := &collectionMock.Collections{}
+	//	for _, collection := range getCollections() {
+	//		collsMock.On("Get", mock.Anything, spaceID, envID, collection.ID).
+	//			Return(collection, nil).
+	//			Once()
+	//	}
+	//
+	//	rMock := &rolesMock.Roles{}
+	//	for _, role := range getRoles() {
+	//		rMock.On("Get", mock.Anything, spaceID, role.ID).
+	//			Return(role, nil).
+	//			Once()
+	//	}
+	//
+	//	clMock := &clientsMock.Clients{}
+	//	for _, client := range getClients() {
+	//		clMock.On("Get", mock.Anything, spaceID, client.ID).
+	//			Return(client, nil).
+	//			Once()
+	//	}
+	//
+	//	itmMock := &itemsMock.Items{}
+	//	for _, act := range getActions() {
+	//		itmMock.On("Get", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
+	//			Return(act, nil).
+	//			Once()
+	//	}
+	//
+	//	itmMock.On(
+	//		"Find", mock.Anything, spaceID, envID, tasks.TaskConfigCollection,
+	//		mock.Anything,
+	//	).
+	//		Return(nil, 0, errors.New("can't get task configs")).
+	//		Once()
+	//
+	//	setup := newSetup(&content.Content{
+	//		Roles:       rMock,
+	//		Clients:     clMock,
+	//		Collections: collsMock,
+	//		Items:       itmMock,
+	//	}, t)
+	//
+	//	err := setup.Check(context.Background())
+	//
+	//	require.Error(t, err)
+	//	assert.ErrorContains(t, err, "task configs check error")
+	//
+	//	rMock.AssertExpectations(t)
+	//	clMock.AssertExpectations(t)
+	//	collsMock.AssertExpectations(t)
+	//})
+}