diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 8b287fcc..42d7d552 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -4,7 +4,7 @@ "description": "REST API for Cosmos Cloud server management", "title": "Cosmos Server API", "contact": {}, - "version": "0.22.0-unstable13" + "version": "0.22.0-unstable14" }, "paths": { "/_logs": { @@ -7817,6 +7817,68 @@ } } }, + "/api/setup": { + "post": { + "description": "Performs the entire initial setup in a single call: database, HTTPS, admin user, optional Constellation connect, and optional admin API token creation. Only available when the server is in NewInstall mode.", + "tags": [ + "system" + ], + "summary": "One-shot server provisioning", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/main.SetupJSON" + } + } + }, + "description": "Setup configuration", + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/utils.APIResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/utils.HTTPErrorResult" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/utils.HTTPErrorResult" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/utils.HTTPErrorResult" + } + } + } + } + } + } + }, "/api/smart-def": { "get": { "security": [ @@ -10759,6 +10821,71 @@ } } }, + "main.SetupJSON": { + "type": "object", + "properties": { + "DNSChallengeConfig": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "allowHTTPLocalIPAccess": { + "type": "boolean" + }, + "clearConfig": { + "description": "Optional: clear config before setup", + "type": "boolean" + }, + "constellationConfig": { + "description": "Optional: Constellation connect", + "type": "string" + }, + "createAdminToken": { + "description": "Optional: create admin API token", + "type": "boolean" + }, + "dnsChallengeProvider": { + "type": "string" + }, + "email": { + "type": "string" + }, + "hostname": { + "description": "HTTPS", + "type": "string" + }, + "httpsCertificateMode": { + "type": "string" + }, + "mongodb": { + "type": "string" + }, + "mongodbMode": { + "description": "Database", + "type": "string" + }, + "nickname": { + "description": "Admin", + "type": "string" + }, + "password": { + "type": "string" + }, + "sslEmail": { + "type": "string" + }, + "tlsCert": { + "type": "string" + }, + "tlsKey": { + "type": "string" + }, + "useWildcardCertificate": { + "type": "boolean" + } + } + }, "mount.BindOptions": { "type": "object", "properties": { diff --git a/api-docs/swagger.json b/api-docs/swagger.json index d02487af..5dd79f79 100644 --- a/api-docs/swagger.json +++ b/api-docs/swagger.json @@ -4,7 +4,7 @@ "description": "REST API for Cosmos Cloud server management", "title": "Cosmos Server API", "contact": {}, - "version": "0.22.0-unstable13" + "version": "0.22.0-unstable14" }, "basePath": "/cosmos", "paths": { @@ -6442,6 +6442,58 @@ } } }, + "/api/setup": { + "post": { + "description": "Performs the entire initial setup in a single call: database, HTTPS, admin user, optional Constellation connect, and optional admin API token creation. Only available when the server is in NewInstall mode.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "system" + ], + "summary": "One-shot server provisioning", + "parameters": [ + { + "description": "Setup configuration", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.SetupJSON" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/utils.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.HTTPErrorResult" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/utils.HTTPErrorResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.HTTPErrorResult" + } + } + } + } + }, "/api/smart-def": { "get": { "security": [ @@ -9000,6 +9052,71 @@ } } }, + "main.SetupJSON": { + "type": "object", + "properties": { + "DNSChallengeConfig": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "allowHTTPLocalIPAccess": { + "type": "boolean" + }, + "clearConfig": { + "description": "Optional: clear config before setup", + "type": "boolean" + }, + "constellationConfig": { + "description": "Optional: Constellation connect", + "type": "string" + }, + "createAdminToken": { + "description": "Optional: create admin API token", + "type": "boolean" + }, + "dnsChallengeProvider": { + "type": "string" + }, + "email": { + "type": "string" + }, + "hostname": { + "description": "HTTPS", + "type": "string" + }, + "httpsCertificateMode": { + "type": "string" + }, + "mongodb": { + "type": "string" + }, + "mongodbMode": { + "description": "Database", + "type": "string" + }, + "nickname": { + "description": "Admin", + "type": "string" + }, + "password": { + "type": "string" + }, + "sslEmail": { + "type": "string" + }, + "tlsCert": { + "type": "string" + }, + "tlsKey": { + "type": "string" + }, + "useWildcardCertificate": { + "type": "boolean" + } + } + }, "mount.BindOptions": { "type": "object", "properties": { diff --git a/api-docs/swagger.yaml b/api-docs/swagger.yaml index a1dbac2e..7738a2f3 100644 --- a/api-docs/swagger.yaml +++ b/api-docs/swagger.yaml @@ -753,6 +753,51 @@ definitions: useWildcardCertificate: type: boolean type: object + main.SetupJSON: + properties: + DNSChallengeConfig: + additionalProperties: + type: string + type: object + allowHTTPLocalIPAccess: + type: boolean + clearConfig: + description: 'Optional: clear config before setup' + type: boolean + constellationConfig: + description: 'Optional: Constellation connect' + type: string + createAdminToken: + description: 'Optional: create admin API token' + type: boolean + dnsChallengeProvider: + type: string + email: + type: string + hostname: + description: HTTPS + type: string + httpsCertificateMode: + type: string + mongodb: + type: string + mongodbMode: + description: Database + type: string + nickname: + description: Admin + type: string + password: + type: string + sslEmail: + type: string + tlsCert: + type: string + tlsKey: + type: string + useWildcardCertificate: + type: boolean + type: object mount.BindOptions: properties: createMountpoint: @@ -1929,7 +1974,7 @@ info: contact: {} description: REST API for Cosmos Cloud server management title: Cosmos Server API - version: 0.22.0-unstable13 + version: 0.22.0-unstable14 paths: /_logs: get: @@ -5993,6 +6038,42 @@ paths: summary: Update a Docker container's settings tags: - docker + /api/setup: + post: + consumes: + - application/json + description: 'Performs the entire initial setup in a single call: database, + HTTPS, admin user, optional Constellation connect, and optional admin API + token creation. Only available when the server is in NewInstall mode.' + parameters: + - description: Setup configuration + in: body + name: request + required: true + schema: + $ref: '#/definitions/main.SetupJSON' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/utils.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.HTTPErrorResult' + "403": + description: Forbidden + schema: + $ref: '#/definitions/utils.HTTPErrorResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.HTTPErrorResult' + summary: One-shot server provisioning + tags: + - system /api/smart-def: get: produces: diff --git a/go-sdk/client.gen.go b/go-sdk/client.gen.go index cee6d96d..8c8fed43 100644 --- a/go-sdk/client.gen.go +++ b/go-sdk/client.gen.go @@ -642,6 +642,39 @@ type MainNewInstallJSON struct { UseWildcardCertificate *bool `json:"useWildcardCertificate,omitempty"` } +// MainSetupJSON defines model for main.SetupJSON. +type MainSetupJSON struct { + DNSChallengeConfig *map[string]string `json:"DNSChallengeConfig,omitempty"` + AllowHTTPLocalIPAccess *bool `json:"allowHTTPLocalIPAccess,omitempty"` + + // ClearConfig Optional: clear config before setup + ClearConfig *bool `json:"clearConfig,omitempty"` + + // ConstellationConfig Optional: Constellation connect + ConstellationConfig *string `json:"constellationConfig,omitempty"` + + // CreateAdminToken Optional: create admin API token + CreateAdminToken *bool `json:"createAdminToken,omitempty"` + DnsChallengeProvider *string `json:"dnsChallengeProvider,omitempty"` + Email *string `json:"email,omitempty"` + + // Hostname HTTPS + Hostname *string `json:"hostname,omitempty"` + HttpsCertificateMode *string `json:"httpsCertificateMode,omitempty"` + Mongodb *string `json:"mongodb,omitempty"` + + // MongodbMode Database + MongodbMode *string `json:"mongodbMode,omitempty"` + + // Nickname Admin + Nickname *string `json:"nickname,omitempty"` + Password *string `json:"password,omitempty"` + SslEmail *string `json:"sslEmail,omitempty"` + TlsCert *string `json:"tlsCert,omitempty"` + TlsKey *string `json:"tlsKey,omitempty"` + UseWildcardCertificate *bool `json:"useWildcardCertificate,omitempty"` +} + // MountBindOptions defines model for mount.BindOptions. type MountBindOptions struct { CreateMountpoint *bool `json:"createMountpoint,omitempty"` @@ -1601,6 +1634,9 @@ type PutApiRoutesNameJSONRequestBody = UtilsProxyRouteConfig // PostApiServappsContainerIdUpdateJSONRequestBody defines body for PostApiServappsContainerIdUpdate for application/json ContentType. type PostApiServappsContainerIdUpdateJSONRequestBody = DockerContainerForm +// PostApiSetupJSONRequestBody defines body for PostApiSetup for application/json ContentType. +type PostApiSetupJSONRequestBody = MainSetupJSON + // GetApiSnapraidJSONRequestBody defines body for GetApiSnapraid for application/json ContentType. type GetApiSnapraidJSONRequestBody = UtilsSnapRAIDConfig @@ -2191,6 +2227,11 @@ type ClientInterface interface { PostApiServappsContainerIdUpdate(ctx context.Context, containerId string, body PostApiServappsContainerIdUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostApiSetupWithBody request with any body + PostApiSetupWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PostApiSetup(ctx context.Context, body PostApiSetupJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetApiSmartDef request GetApiSmartDef(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -4380,6 +4421,30 @@ func (c *Client) PostApiServappsContainerIdUpdate(ctx context.Context, container return c.Client.Do(req) } +func (c *Client) PostApiSetupWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostApiSetupRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostApiSetup(ctx context.Context, body PostApiSetupJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostApiSetupRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetApiSmartDef(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetApiSmartDefRequest(c.Server) if err != nil { @@ -9781,6 +9846,46 @@ func NewPostApiServappsContainerIdUpdateRequestWithBody(server string, container return req, nil } +// NewPostApiSetupRequest calls the generic PostApiSetup builder with application/json body +func NewPostApiSetupRequest(server string, body PostApiSetupJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostApiSetupRequestWithBody(server, "application/json", bodyReader) +} + +// NewPostApiSetupRequestWithBody generates requests for PostApiSetup with any type of body +func NewPostApiSetupRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/setup") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewGetApiSmartDefRequest generates requests for GetApiSmartDef func NewGetApiSmartDefRequest(server string) (*http.Request, error) { var err error @@ -11321,6 +11426,11 @@ type ClientWithResponsesInterface interface { PostApiServappsContainerIdUpdateWithResponse(ctx context.Context, containerId string, body PostApiServappsContainerIdUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiServappsContainerIdUpdateResponse, error) + // PostApiSetupWithBodyWithResponse request with any body + PostApiSetupWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiSetupResponse, error) + + PostApiSetupWithResponse(ctx context.Context, body PostApiSetupJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiSetupResponse, error) + // GetApiSmartDefWithResponse request GetApiSmartDefWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiSmartDefResponse, error) @@ -14542,6 +14652,31 @@ func (r PostApiServappsContainerIdUpdateResponse) StatusCode() int { return 0 } +type PostApiSetupResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *UtilsAPIResponse + JSON400 *UtilsHTTPErrorResult + JSON403 *UtilsHTTPErrorResult + JSON500 *UtilsHTTPErrorResult +} + +// Status returns HTTPResponse.Status +func (r PostApiSetupResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostApiSetupResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetApiSmartDefResponse struct { Body []byte HTTPResponse *http.Response @@ -16749,6 +16884,23 @@ func (c *ClientWithResponses) PostApiServappsContainerIdUpdateWithResponse(ctx c return ParsePostApiServappsContainerIdUpdateResponse(rsp) } +// PostApiSetupWithBodyWithResponse request with arbitrary body returning *PostApiSetupResponse +func (c *ClientWithResponses) PostApiSetupWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiSetupResponse, error) { + rsp, err := c.PostApiSetupWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostApiSetupResponse(rsp) +} + +func (c *ClientWithResponses) PostApiSetupWithResponse(ctx context.Context, body PostApiSetupJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiSetupResponse, error) { + rsp, err := c.PostApiSetup(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostApiSetupResponse(rsp) +} + // GetApiSmartDefWithResponse request returning *GetApiSmartDefResponse func (c *ClientWithResponses) GetApiSmartDefWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiSmartDefResponse, error) { rsp, err := c.GetApiSmartDef(ctx, reqEditors...) @@ -22504,6 +22656,53 @@ func ParsePostApiServappsContainerIdUpdateResponse(rsp *http.Response) (*PostApi return response, nil } +// ParsePostApiSetupResponse parses an HTTP response from a PostApiSetupWithResponse call +func ParsePostApiSetupResponse(rsp *http.Response) (*PostApiSetupResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostApiSetupResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest UtilsAPIResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest UtilsHTTPErrorResult + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest UtilsHTTPErrorResult + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest UtilsHTTPErrorResult + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseGetApiSmartDefResponse parses an HTTP response from a GetApiSmartDefWithResponse call func ParseGetApiSmartDefResponse(rsp *http.Response) (*GetApiSmartDefResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -23763,288 +23962,293 @@ func ParsePostRcloneVfsStatsResponse(rsp *http.Response) (*PostRcloneVfsStatsRes // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "TqF5wclea9HThna04QfX7HjBicnKFUQ2Km/m9Kki9Rx+Za5ifmPxEOvAomlXhW5NVyjvHq4ZYijSxbpZ", + "c1FyPXxpTN+Tcf21rx7Sk5q/qRhSFDUJYPmWYiYGjfNC0Lm+RqYQkRmBOIzVpcfMS0fpH1FxwD5k+odL", + "TzG+P8WwpVUM4lf5tucBg1W08GmgY1wUys+Jh8yiBcISQYIJNbZ7wamnmooe8lmRjid6H53ERG15HZ0S", + "oLG0ymUXmds/jHra8zMblGOiwrJK/iJZcpolcPjV/veitVf9J9P+U9FrG1nRbfLa2XYujYtuGi+VWZbH", + "7vO7/I2yfDkp2wVxO2ipe8XWwrdty23ZiT65Vj2c/FxFc5bFvXZN/laGiCdLlWEnqWhyfQdjm631uH2+", + "jN5t5vuoLitoFCKzlM95K3fkYy4TLnMfGd3NZqu/uvi9MA7XqPMHPfpWwDbdD1M2rx7qdl+RAAz/o+0M", + "3y13gX42ugOk9ki8dnQRUc7g0Hp2H7qUy40egWPTw4Y0Hecpmnclels968aQ8KKIpTW4aRY+RLqn9Tkv", + "yYy9U2BP3Z6Ouo2PNeSbvPCV2o/2dy9ClQ0zTa+hMlKN8gfNkyHVpfmyxib9qLK7qjyoelx6hri0yzOI", + "7QS6WZI2PYMqIKvb9klsOt1NlqRGyplllOb3s5m0qsU1USKVvVbZ9rY+lLr0l9b5vVjBJYn2DAU1+WNX", + "KZhCN0PEuH5+xcAUwVS+aneVEi87yF4TvIT++rpl/MRLMDi3O7qVsnlmvkvKKnf0sWwneEr52GXGXHP0", + "XjDuBePvkSaTIbgnUhE27yhjCDCRya2Ylk183TOrl27GnlM+xYWwowRmcubCi+UWgClgQx7iKc9UGz55", + "WfQ5Ml12yONiWvbEsieWNdjXq3nzY1JLnCUpNVWqIO6Ogy7eFmVS/z9hM+7K7zkFwJ7iCtMhyiTEQzQT", + "ANuk1+VMrkn5Npz8NJNrWt4j499CDaHB7tPpxNJ5B20m1hbiJmnBTKFRzvoCZIIO3g0OI6PTHnz78u3/", + "BQAA//9nKpErAYUCAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/go-sdk/version.go b/go-sdk/version.go index f5c8f814..ce053192 100644 --- a/go-sdk/version.go +++ b/go-sdk/version.go @@ -1,4 +1,4 @@ package cosmossdk // Version is the SDK version, synced with Cosmos Server at build time. -const Version = "0.22.0-unstable13" +const Version = "0.22.0-unstable14" diff --git a/package.json b/package.json index 77eb4988..b230becf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.22.0-unstable13", + "version": "0.22.0-unstable14", "description": "", "main": "test-server.js", "bugs": { @@ -90,7 +90,7 @@ "scripts": { "client": "vite", "client-build": "vite build --base=/cosmos-ui/", - "start": "build/cosmos-launcher && env COSMOS_CONFIG_FOLDER=./zz_test_config/ CONFIG_FILE=./config_dev.json EZ=UTC ACME_STAGING=true build/cosmos", + "start": "build/cosmos-launcher && env COSMOS_LOG_LEVEL=DEBUG COSMOS_CONFIG_FOLDER=./zz_test_config/ CONFIG_FILE=./config_dev.json EZ=UTC ACME_STAGING=true build/cosmos", "build": "sh build-dev.sh", "dev": "npm run build && npm run start", "dockerdevbuild": "docker build -f dockerfile.local --tag cosmos-dev .", diff --git a/sdk/src/index.ts b/sdk/src/index.ts index 85962ff0..9b5925ae 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -14,6 +14,26 @@ import createApiTokensAPI from '../../client/src/api/apiTokens'; import createOpenIDAPI from '../../client/src/api/openid'; import wrap from '../../client/src/api/wrap'; +export interface SetupRequest { + mongodbMode: string; + mongodb?: string; + hostname: string; + httpsCertificateMode: string; + sslEmail?: string; + useWildcardCertificate?: boolean; + dnsChallengeProvider?: string; + DNSChallengeConfig?: Record; + tlsCert?: string; + tlsKey?: string; + allowHTTPLocalIPAccess?: boolean; + nickname: string; + password: string; + email?: string; + clearConfig?: boolean; + constellationConfig?: string; + createAdminToken?: boolean; +} + export function createClient({ baseUrl, token }: { baseUrl: string; token: string }) { const { apiFetch, createWs } = createApiClient(baseUrl, token); @@ -32,6 +52,14 @@ export function createClient({ baseUrl, token }: { baseUrl: string; token: strin apiTokens: createApiTokensAPI(apiFetch), openid: createOpenIDAPI(apiFetch), + setup: (request: SetupRequest) => { + return wrap(apiFetch('/cosmos/api/setup', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request), + })); + }, + getStatus: () => { return wrap(apiFetch('/cosmos/api/status', { method: 'GET', diff --git a/src/configapi/api_tokens.go b/src/configapi/api_tokens.go index 37609965..c1c6bc87 100644 --- a/src/configapi/api_tokens.go +++ b/src/configapi/api_tokens.go @@ -17,6 +17,33 @@ import ( var validTokenName = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`) +// GenerateAPIToken creates a new API token with the given parameters and returns +// the raw token (to be shown once to the caller) and the config to store. +// The caller is responsible for persisting the config. +func GenerateAPIToken(name, description, owner string, permissions []utils.Permission) (string, utils.APITokenConfig, error) { + rawBytes := make([]byte, 32) + if _, err := rand.Read(rawBytes); err != nil { + return "", utils.APITokenConfig{}, err + } + rawToken := "cosmos_" + base64.RawURLEncoding.EncodeToString(rawBytes) + tokenSuffix := rawToken[len(rawToken)-5:] + + h := sha256.Sum256([]byte(rawToken)) + tokenHash := hex.EncodeToString(h[:]) + + tokenConfig := utils.APITokenConfig{ + Name: name, + Description: description, + Owner: owner, + TokenHash: tokenHash, + TokenSuffix: tokenSuffix, + Permissions: permissions, + CreatedAt: time.Now(), + } + + return rawToken, tokenConfig, nil +} + type CreateAPITokenRequest struct { Name string `json:"name" validate:"required"` Description string `json:"description,omitempty"` @@ -129,20 +156,6 @@ func createAPIToken(w http.ResponseWriter, req *http.Request) { return } - // Generate raw token: "cosmos_" + base64url(32 random bytes) - rawBytes := make([]byte, 32) - if _, err := rand.Read(rawBytes); err != nil { - utils.Error("CreateAPIToken: Failed to generate random bytes", err) - utils.HTTPError(w, "Internal error", http.StatusInternalServerError, "AT012") - return - } - rawToken := "cosmos_" + base64.RawURLEncoding.EncodeToString(rawBytes) - tokenSuffix := rawToken[len(rawToken)-5:] - - // Hash for storage - h := sha256.Sum256([]byte(rawToken)) - tokenHash := hex.EncodeToString(h[:]) - // Build permissions array var permissions []utils.Permission if len(request.Permissions) > 0 { @@ -155,37 +168,23 @@ func createAPIToken(w http.ResponseWriter, req *http.Request) { utils.PERM_CONFIGURATION_READ, } } else { - permissions = []utils.Permission{ - utils.PERM_ADMIN_READ, - utils.PERM_ADMIN, - utils.PERM_USERS_READ, - utils.PERM_USERS, - utils.PERM_RESOURCES_READ, - utils.PERM_RESOURCES, - utils.PERM_CONFIGURATION_READ, - utils.PERM_CONFIGURATION, - utils.PERM_CREDENTIALS_READ, - } + permissions = utils.DefaultAdminTokenPermissions } owner := utils.GetAuthContext(req).Nickname - var expiresAt time.Time - if request.ExpiryDays > 0 { - expiresAt = time.Now().Add(time.Duration(request.ExpiryDays) * 24 * time.Hour) + rawToken, tokenConfig, err := GenerateAPIToken(request.Name, request.Description, owner, permissions) + if err != nil { + utils.Error("CreateAPIToken: Failed to generate token", err) + utils.HTTPError(w, "Internal error", http.StatusInternalServerError, "AT012") + return } - tokenConfig := utils.APITokenConfig{ - Name: request.Name, - Description: request.Description, - Owner: owner, - TokenHash: tokenHash, - TokenSuffix: tokenSuffix, - Permissions: permissions, - IPWhitelist: request.IPWhitelist, - RestrictToConstellation: request.RestrictToConstellation, - CreatedAt: time.Now(), - ExpiresAt: expiresAt, + // Set extra fields not handled by GenerateAPIToken + tokenConfig.IPWhitelist = request.IPWhitelist + tokenConfig.RestrictToConstellation = request.RestrictToConstellation + if request.ExpiryDays > 0 { + tokenConfig.ExpiresAt = time.Now().Add(time.Duration(request.ExpiryDays) * 24 * time.Hour) } utils.ConfigLock.Lock() @@ -223,10 +222,10 @@ func createAPIToken(w http.ResponseWriter, req *http.Request) { responseData := map[string]interface{}{ "token": rawToken, "name": request.Name, - "tokenSuffix": tokenSuffix, + "tokenSuffix": tokenConfig.TokenSuffix, } - if !expiresAt.IsZero() { - responseData["expiresAt"] = expiresAt + if !tokenConfig.ExpiresAt.IsZero() { + responseData["expiresAt"] = tokenConfig.ExpiresAt } json.NewEncoder(w).Encode(map[string]interface{}{ diff --git a/src/constellation/api_nebula_connect.go b/src/constellation/api_nebula_connect.go index 22ddde5b..d82d7def 100644 --- a/src/constellation/api_nebula_connect.go +++ b/src/constellation/api_nebula_connect.go @@ -1,9 +1,10 @@ package constellation import ( - "net/http" "encoding/json" + "errors" "io/ioutil" + "net/http" "os" "gopkg.in/yaml.v2" @@ -156,6 +157,58 @@ func API_NewConstellation(w http.ResponseWriter, req *http.Request) { } } +// ConnectToExisting applies a Nebula YAML config to connect this node to an +// existing Constellation network. It returns the updated config. The caller +// is responsible for persisting the config and restarting Nebula. +func ConnectToExisting(yamlBody []byte, config utils.Config) (utils.Config, error) { + utils.Log("ConnectToExisting: connecting to an external Constellation") + + config.ConstellationConfig.Enabled = true + + var configMap map[string]interface{} + err := yaml.Unmarshal(yamlBody, &configMap) + if err != nil { + return config, err + } + + configMap = setDefaultConstConfig(configMap) + + configMapString, err := yaml.Marshal(configMap) + if err != nil { + return config, err + } + + err = ioutil.WriteFile(utils.CONFIGFOLDER+"nebula.yml", configMapString, 0600) + if err != nil { + return config, err + } + + if deviceNameVal, ok := configMap["cstln_device_name"]; ok { + config.ConstellationConfig.ThisDeviceName = deviceNameVal.(string) + } else { + return config, errors.New("device name not found in constellation config") + } + + if publicHostnameVal, ok := configMap["cstln_public_hostname"]; ok { + config.ConstellationConfig.ConstellationHostname = publicHostnameVal.(string) + } + + if licence, ok := configMap["cstln_server_licence"]; ok { + config.Licence = licence.(string) + } + + if cosmosNode, ok := configMap["cstln_cosmos_node"]; ok { + config.AgentMode = cosmosNode.(int) == 1 + } + + if ipRange, ok := configMap["cstln_ip_range"]; ok { + config.ConstellationConfig.IPRange = ipRange.(string) + } + + utils.Log("ConnectToExisting: connected to an external Constellation") + return config, nil +} + // API_ConnectToExisting godoc // @Summary Connect this node to an existing Constellation VPN network // @Tags constellation @@ -176,69 +229,25 @@ func API_ConnectToExisting(w http.ResponseWriter, req *http.Request) { utils.ConfigLock.Lock() defer utils.ConfigLock.Unlock() - utils.Log("API_ConnectToExisting: connecting to an external Constellation") - body, err := ioutil.ReadAll(req.Body) if err != nil { - utils.Error("API_Restart: Invalid User Request", err) - utils.HTTPError(w, "API_Restart Error", + utils.Error("API_ConnectToExisting: Invalid User Request", err) + utils.HTTPError(w, "API_ConnectToExisting Error", http.StatusInternalServerError, "AR001") - return + return } config := utils.ReadConfigFromFile() - config.ConstellationConfig.Enabled = true - - var configMap map[string]interface{} - - err = yaml.Unmarshal(body, &configMap) + config, err = ConnectToExisting(body, config) if err != nil { - utils.Error("API_ConnectToExisting: Invalid User Request", err) - utils.HTTPError(w, "API_ConnectToExisting Error", + utils.Error("API_ConnectToExisting: Error", err) + utils.HTTPError(w, "API_ConnectToExisting Error: "+err.Error(), http.StatusInternalServerError, "ACE001") - return - } - - configMap = setDefaultConstConfig(configMap) - - configMapString, err := yaml.Marshal(configMap) - if err != nil { - utils.Error("API_ConnectToExisting: Invalid User Request", err) - utils.HTTPError(w, "API_ConnectToExisting Error", - http.StatusInternalServerError, "ACE002") return } - // output utils.CONFIGFOLDER + "nebula.yml" - err = ioutil.WriteFile(utils.CONFIGFOLDER + "nebula.yml", configMapString, 0600) - - if deviceNameVal, ok := configMap["cstln_device_name"]; ok { - config.ConstellationConfig.ThisDeviceName = deviceNameVal.(string) - } else { - utils.Error("API_ConnectToExisting: device name not found in config", nil) - utils.HTTPError(w, "API_ConnectToExisting Error: device name not found in config", - http.StatusInternalServerError, "ACE003") - return - } - - if publicHostnameVal, ok := configMap["cstln_public_hostname"]; ok { - config.ConstellationConfig.ConstellationHostname = publicHostnameVal.(string) - } - - if licence, ok := configMap["cstln_server_licence"]; ok { - config.Licence = licence.(string) - } - - if cosmosNode, ok := configMap["cstln_cosmos_node"]; ok { - config.AgentMode = cosmosNode.(int) == 1 - } - - if ipRange, ok := configMap["cstln_ip_range"]; ok { - config.ConstellationConfig.IPRange = ipRange.(string) - } - utils.SetBaseMainConfig(config) - + utils.TriggerEvent( "cosmos.settings", "Settings updated", @@ -247,14 +256,11 @@ func API_ConnectToExisting(w http.ResponseWriter, req *http.Request) { map[string]interface{}{ "from": "Constellation", }) - - utils.Log("API_ConnectToExisting: connected to an external Constellation") - json.NewEncoder(w).Encode(map[string]interface{}{ "status": "OK", }) - + go func() { RestartNebula() utils.RestartHTTPServer() diff --git a/src/httpServer.go b/src/httpServer.go index 2b6ec933..8dfd0319 100644 --- a/src/httpServer.go +++ b/src/httpServer.go @@ -611,6 +611,7 @@ func InitServer() *mux.Router { srapiStrict.HandleFunc("/api/mfa", user.API2FA) srapiStrict.HandleFunc("/api/register", user.UserRegister) srapiStrict.HandleFunc("/api/newInstall", NewInstallRoute) + srapiStrict.HandleFunc("/api/setup", SetupRoute) srapi.HandleFunc("/api/status", StatusRoute) srapi.HandleFunc("/api/can-send-email", CanSendEmail) srapi.HandleFunc("/api/logout", user.UserLogout) diff --git a/src/index.go b/src/index.go index fc3392f1..e69bfebc 100644 --- a/src/index.go +++ b/src/index.go @@ -201,7 +201,7 @@ func main() { } // @title Cosmos Server API -// @version 0.22.0-unstable12 +// @version 0.22.0-unstable13 // @description REST API for Cosmos Cloud server management // @BasePath /cosmos // @securityDefinitions.apikey BearerAuth diff --git a/src/newInstall.go b/src/newInstall.go index 5bb0aa7f..b655cf1b 100644 --- a/src/newInstall.go +++ b/src/newInstall.go @@ -1,20 +1,24 @@ package main import ( - "net/http" "encoding/json" - "time" + "net/http" "os" - "golang.org/x/crypto/bcrypt" + "time" - "github.com/azukaar/cosmos-server/src/utils" + "golang.org/x/crypto/bcrypt" + + "github.com/azukaar/cosmos-server/src/configapi" + "github.com/azukaar/cosmos-server/src/constellation" "github.com/azukaar/cosmos-server/src/docker" + "github.com/azukaar/cosmos-server/src/utils" ) func waitForDB() { time.Sleep(1 * time.Second) err := utils.DB() if err != nil { + utils.Debug("DB error: " + err.Error()) utils.Warn("DB Not ready yet") waitForDB() } @@ -40,7 +44,7 @@ type NewInstallJSON struct { type AdminJSON struct { Nickname string `validate:"required,min=3,max=32,alphanum"` - Password string `validate:"required,min=9,max=128,containsany=~!@#$%^&*()_+=-{[}]:;"'<>.?/,containsany=ABCDEFGHIJKLMNOPQRSTUVWXYZ,containsany=abcdefghijklmnopqrstuvwxyz,containsany=0123456789"` + Password string `validate:"required,min=9,max=128,containsany=~!@#$%^&*()_+=-{[}]:;\"'<>.?/,containsany=ABCDEFGHIJKLMNOPQRSTUVWXYZ,containsany=abcdefghijklmnopqrstuvwxyz,containsany=0123456789"` } // NewInstallRoute godoc @@ -67,9 +71,9 @@ func NewInstallRoute(w http.ResponseWriter, req *http.Request) { err1 := json.NewDecoder(req.Body).Decode(&request) if err1 != nil { utils.Error("NewInstall: Invalid User Request", err1) - utils.HTTPError(w, "New Install: Invalid User Request" + err1.Error(), + utils.HTTPError(w, "New Install: Invalid User Request" + err1.Error(), http.StatusInternalServerError, "NI001") - return + return } errV := utils.Validate.Struct(request) @@ -77,7 +81,7 @@ func NewInstallRoute(w http.ResponseWriter, req *http.Request) { utils.Error("NewInstall: Invalid User Request", errV) utils.HTTPError(w, "New Install: Invalid User Request " + errV.Error(), http.StatusInternalServerError, "NI001") - return + return } newConfig := utils.GetBaseMainConfig() @@ -108,18 +112,20 @@ func NewInstallRoute(w http.ResponseWriter, req *http.Request) { utils.Log("NewInstall: DB Provided") newConfig.DisableUserManagement = false newConfig.MongoDB = request.MongoDB + newConfig.Database.PuppetMode = false utils.SaveConfigTofile(newConfig) utils.LoadBaseMainConfig(newConfig) } else if (request.MongoDBMode == "Create") { utils.Log("NewInstall: Create DB") newConfig.DisableUserManagement = false + newConfig.MongoDB = "" strco, err := docker.NewDB(w, req) if err != nil { utils.Error("NewInstall: Error creating MongoDB", err) - return - } - + return + } + newConfig.Database = strco utils.SaveConfigTofile(newConfig) utils.LoadBaseMainConfig(newConfig) @@ -132,7 +138,7 @@ func NewInstallRoute(w http.ResponseWriter, req *http.Request) { utils.Error("NewInstall: Invalid MongoDBMode", nil) utils.HTTPError(w, "New Install: Invalid MongoDBMode", http.StatusInternalServerError, "NI001") - return + return } } else if (request.Step == "3") { // HTTPS Certificate Mode & Certs & Let's Encrypt @@ -154,7 +160,7 @@ func NewInstallRoute(w http.ResponseWriter, req *http.Request) { adminObj := AdminJSON{ Nickname: request.Nickname, Password: request.Password, - } + } errV2 := utils.Validate.Struct(adminObj) if errV2 != nil { @@ -221,4 +227,235 @@ func NewInstallRoute(w http.ResponseWriter, req *http.Request) { utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") return } -} \ No newline at end of file +} + +// noopResponseWriter discards all output — used to call docker.NewDB without streaming. +type noopResponseWriter struct { + header http.Header +} + +func (n *noopResponseWriter) Header() http.Header { return n.header } +func (n *noopResponseWriter) Write(b []byte) (int, error) { return len(b), nil } +func (n *noopResponseWriter) WriteHeader(int) {} +func (n *noopResponseWriter) Flush() {} + +type SetupJSON struct { + // Database + MongoDBMode string `json:"mongodbMode"` + MongoDB string `json:"mongodb"` + + // HTTPS + Hostname string `json:"hostname"` + HTTPSCertificateMode string `json:"httpsCertificateMode"` + SSLEmail string `json:"sslEmail"` + UseWildcardCertificate bool `json:"useWildcardCertificate"` + DNSChallengeProvider string `json:"dnsChallengeProvider"` + DNSChallengeConfig map[string]string `json:"DNSChallengeConfig"` + TLSCert string `json:"tlsCert"` + TLSKey string `json:"tlsKey"` + AllowHTTPLocalIPAccess bool `json:"allowHTTPLocalIPAccess"` + + // Admin + Nickname string `json:"nickname"` + Password string `json:"password"` + Email string `json:"email,omitempty"` + + // Optional: clear config before setup + ClearConfig bool `json:"clearConfig,omitempty"` + + // Optional: Constellation connect + ConstellationConfig string `json:"constellationConfig,omitempty"` + + // Optional: create admin API token + CreateAdminToken bool `json:"createAdminToken,omitempty"` +} + +// SetupRoute godoc +// @Summary One-shot server provisioning +// @Description Performs the entire initial setup in a single call: database, HTTPS, admin user, optional Constellation connect, and optional admin API token creation. Only available when the server is in NewInstall mode. +// @Tags system +// @Accept json +// @Produce json +// @Param request body SetupJSON true "Setup configuration" +// @Success 200 {object} utils.APIResponse +// @Failure 403 {object} utils.HTTPErrorResult +// @Failure 400 {object} utils.HTTPErrorResult +// @Failure 500 {object} utils.HTTPErrorResult +// @Router /api/setup [post] +func SetupRoute(w http.ResponseWriter, req *http.Request) { + if !utils.GetMainConfig().NewInstall { + utils.Error("Setup: not a new install", nil) + utils.HTTPError(w, "Server is already configured", http.StatusForbidden, "SU001") + return + } + + if req.Method != "POST" { + utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") + return + } + + var request SetupJSON + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + utils.Error("Setup: Invalid request", err) + utils.HTTPError(w, "Invalid request: "+err.Error(), http.StatusBadRequest, "SU002") + return + } + + // ── Step 0: Clear config (optional) ────────────────────────────── + if request.ClearConfig { + utils.Log("Setup: Clearing config") + configFile := utils.GetConfigFileName() + os.Remove(configFile) + if utils.IsInsideContainer { + os.RemoveAll("/config") + os.Mkdir("/config", 0700) + } + utils.DisconnectDB() + LoadConfig() + } + + config := utils.GetBaseMainConfig() + + // ── Step 1: Database ────────────────────────────────────────────── + if request.MongoDBMode == "" { + utils.HTTPError(w, "mongodbMode is required", http.StatusBadRequest, "SU003") + return + } + + switch request.MongoDBMode { + case "DisableUserManagement": + utils.Log("Setup: Disable User Management") + config.DisableUserManagement = true + case "Provided": + utils.Log("Setup: DB Provided") + config.DisableUserManagement = false + config.MongoDB = request.MongoDB + config.Database.PuppetMode = false + case "Create": + utils.Log("Setup: Create DB") + config.DisableUserManagement = false + config.MongoDB = "" + noop := &noopResponseWriter{header: make(http.Header)} + dbConf, err := docker.NewDB(noop, req) + if err != nil { + utils.Error("Setup: Error creating MongoDB", err) + utils.HTTPError(w, "Error creating MongoDB: "+err.Error(), http.StatusInternalServerError, "SU004") + return + } + config.Database = dbConf + default: + utils.HTTPError(w, "Invalid mongodbMode", http.StatusBadRequest, "SU003") + return + } + + utils.SaveConfigTofile(config) + utils.LoadBaseMainConfig(config) + + if !config.DisableUserManagement { + waitForDB() + } + + // ── Step 2: HTTPS ───────────────────────────────────────────────── + config.HTTPConfig.Hostname = request.Hostname + config.HTTPConfig.HTTPSCertificateMode = request.HTTPSCertificateMode + config.HTTPConfig.SSLEmail = request.SSLEmail + config.HTTPConfig.UseWildcardCertificate = request.UseWildcardCertificate + config.HTTPConfig.DNSChallengeProvider = request.DNSChallengeProvider + config.HTTPConfig.DNSChallengeConfig = request.DNSChallengeConfig + config.HTTPConfig.TLSCert = request.TLSCert + config.HTTPConfig.TLSKey = request.TLSKey + config.HTTPConfig.AllowHTTPLocalIPAccess = request.AllowHTTPLocalIPAccess + + utils.SaveConfigTofile(config) + utils.LoadBaseMainConfig(config) + + // ── Step 3: Admin User ──────────────────────────────────────────── + if !config.DisableUserManagement { + adminObj := AdminJSON{ + Nickname: request.Nickname, + Password: request.Password, + } + if errV := utils.Validate.Struct(adminObj); errV != nil { + utils.Error("Setup: Invalid admin credentials", errV) + utils.HTTPError(w, "Invalid admin credentials: "+errV.Error(), http.StatusBadRequest, "SU005") + return + } + + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() + if errCo != nil { + utils.Error("Setup: Database Connect", errCo) + utils.HTTPError(w, "Database error", http.StatusInternalServerError, "SU006") + return + } + + nickname := utils.Sanitize(request.Nickname) + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(request.Password), 14) + if err != nil { + utils.Error("Setup: Error hashing password", err) + utils.HTTPError(w, "Error hashing password", http.StatusInternalServerError, "SU007") + return + } + + c.DeleteMany(nil, map[string]interface{}{}) + _, err = c.InsertOne(nil, map[string]interface{}{ + "Nickname": nickname, + "Email": request.Email, + "Password": hashedPassword, + "Role": utils.ADMIN, + "PasswordCycle": 0, + "CreatedAt": time.Now(), + "RegisteredAt": time.Now(), + }) + if err != nil { + utils.Error("Setup: Error creating admin user", err) + utils.HTTPError(w, "Error creating admin user: "+err.Error(), http.StatusInternalServerError, "SU008") + return + } + } + + // ── Step 4: Constellation (optional) ────────────────────────────── + if request.ConstellationConfig != "" { + var err error + config, err = constellation.ConnectToExisting([]byte(request.ConstellationConfig), config) + if err != nil { + utils.Error("Setup: Constellation connect error", err) + utils.HTTPError(w, "Constellation error: "+err.Error(), http.StatusInternalServerError, "SU009") + return + } + utils.SaveConfigTofile(config) + utils.LoadBaseMainConfig(config) + } + + // ── Step 5: Admin Token (optional) ──────────────────────────────── + responseData := map[string]interface{}{} + + if request.CreateAdminToken { + tokenName := "provisioning-token" + owner := utils.Sanitize(request.Nickname) + rawToken, tokenConfig, err := configapi.GenerateAPIToken(tokenName, "Auto-generated during setup", owner, utils.DefaultAdminTokenPermissions) + if err != nil { + utils.Error("Setup: Error generating token", err) + utils.HTTPError(w, "Error generating token", http.StatusInternalServerError, "SU010") + return + } + + if config.APITokens == nil { + config.APITokens = make(map[string]utils.APITokenConfig) + } + config.APITokens[tokenName] = tokenConfig + + responseData["adminToken"] = rawToken + responseData["adminTokenName"] = tokenName + } + + // ── Finalize ────────────────────────────────────────────────────── + config.NewInstall = false + utils.SaveConfigTofile(config) + utils.LoadBaseMainConfig(config) + + json.NewEncoder(w).Encode(map[string]interface{}{ + "status": "OK", + "data": responseData, + }) +} diff --git a/src/user/token.go b/src/user/token.go index 04192d1f..76ae402b 100644 --- a/src/user/token.go +++ b/src/user/token.go @@ -38,7 +38,7 @@ func RefreshUserToken(w http.ResponseWriter, req *http.Request) ([]utils.Permiss // if new install if config.NewInstall { // check route - if req.URL.Path != "/cosmos/api/status" && req.URL.Path != "/cosmos/api/newInstall" && req.URL.Path != "/cosmos/api/dns" { + if req.URL.Path != "/cosmos/api/status" && req.URL.Path != "/cosmos/api/newInstall" && req.URL.Path != "/cosmos/api/dns" && req.URL.Path != "/cosmos/api/setup" { json.NewEncoder(w).Encode(map[string]interface{}{ "status": "NEW_INSTALL", }) diff --git a/src/utils/db.go b/src/utils/db.go index 3beb3749..d922dae3 100644 --- a/src/utils/db.go +++ b/src/utils/db.go @@ -53,7 +53,7 @@ func DB() error { if mongoURL == "" { DBStatus = false - return errors.New("MongoDB URL is not set, cannot connect to the database.") + return errors.New("MongoDB URL is not set, cannot connect to the database. If you are in the setup, this is not an issue, please proceed with the setup.") } var err error diff --git a/src/utils/types.go b/src/utils/types.go index 018f075d..85921a74 100644 --- a/src/utils/types.go +++ b/src/utils/types.go @@ -39,6 +39,18 @@ const ( PERM_LOGIN_WEAK Permission = 101 // Any logged-in user, no MFA check ) +var DefaultAdminTokenPermissions = []Permission{ + PERM_ADMIN_READ, + PERM_ADMIN, + PERM_USERS_READ, + PERM_USERS, + PERM_RESOURCES_READ, + PERM_RESOURCES, + PERM_CONFIGURATION_READ, + PERM_CONFIGURATION, + PERM_CREDENTIALS_READ, +} + var PermissionLabels = map[Permission]string{ PERM_ADMIN_READ: "Admin Read (view logs)", PERM_ADMIN: "Admin (system ops)", diff --git a/terraform-provider-cosmos/internal/resources/backup.go b/terraform-provider-cosmos/internal/resources/backup.go index 7c94a0e9..d1cc6ae3 100644 --- a/terraform-provider-cosmos/internal/resources/backup.go +++ b/terraform-provider-cosmos/internal/resources/backup.go @@ -19,6 +19,7 @@ import ( var ( _ resource.Resource = &backupResource{} _ resource.ResourceWithImportState = &backupResource{} + _ resource.ResourceWithModifyPlan = &backupResource{} ) // NewBackupResource returns a new resource.Resource for backup configurations. @@ -46,7 +47,7 @@ func (r *backupResource) Metadata(_ context.Context, req resource.MetadataReques func (r *backupResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: "Manages a Cosmos Server backup configuration.", + Description: "Manages a Cosmos Server backup configuration. WARNING: destroying this resource permanently deletes all its snapshots. Use lifecycle { prevent_destroy = true } to protect against accidental deletion.", Attributes: map[string]schema.Attribute{ "name": schema.StringAttribute{ Description: "The unique name of the backup configuration (used as the resource ID). Changing this forces re-creation.", @@ -56,12 +57,18 @@ func (r *backupResource) Schema(_ context.Context, _ resource.SchemaRequest, res }, }, "repository": schema.StringAttribute{ - Description: "The repository path for storing backups.", + Description: "The repository path for storing backups. Changing this forces re-creation.", Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, "source": schema.StringAttribute{ - Description: "The source path to back up.", + Description: "The source path to back up. Changing this forces re-creation.", Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, "crontab": schema.StringAttribute{ Description: "Cron schedule for running backups.", @@ -263,6 +270,17 @@ func (r *backupResource) Delete(ctx context.Context, req resource.DeleteRequest, } } +func (r *backupResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + // If the plan is to destroy (plan state is null), warn about data loss. + if req.Plan.Raw.IsNull() { + resp.Diagnostics.AddWarning( + "Destroying this backup will permanently delete all its snapshots", + "The Cosmos backup delete endpoint removes all snapshots for this backup from the repository. "+ + "This is irreversible. Use lifecycle { prevent_destroy = true } to protect against accidental deletion.", + ) + } +} + func (r *backupResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp) }