diff --git a/packages/server/assets/core/typedefs/server.graphql b/packages/server/assets/core/typedefs/server.graphql index 6f9bb0023..b26b5eb00 100644 --- a/packages/server/assets/core/typedefs/server.graphql +++ b/packages/server/assets/core/typedefs/server.graphql @@ -2,6 +2,11 @@ extend type Query { serverInfo: ServerInfo! } +type ServerMigration { + movedFrom: String + movedTo: String +} + """ Information about this server. """ @@ -25,6 +30,10 @@ type ServerInfo { Base URL of Speckle Automate, if set """ automateUrl: String + """ + Server relocation / migration info + """ + migration: ServerMigration } type Role { diff --git a/packages/server/modules/core/graph/generated/graphql.ts b/packages/server/modules/core/graph/generated/graphql.ts index fc8832d05..87a7c82c4 100644 --- a/packages/server/modules/core/graph/generated/graphql.ts +++ b/packages/server/modules/core/graph/generated/graphql.ts @@ -2089,6 +2089,8 @@ export type ServerInfo = { description?: Maybe; guestModeEnabled: Scalars['Boolean']; inviteOnly?: Maybe; + /** Server relocation / migration info */ + migration?: Maybe; name: Scalars['String']; /** @deprecated Use role constants from the @speckle/shared npm package instead */ roles: Array; @@ -2122,6 +2124,12 @@ export type ServerInviteCreateInput = { serverRole?: InputMaybe; }; +export type ServerMigration = { + __typename?: 'ServerMigration'; + movedFrom?: Maybe; + movedTo?: Maybe; +}; + export enum ServerRole { ServerAdmin = 'SERVER_ADMIN', ServerArchivedUser = 'SERVER_ARCHIVED_USER', @@ -3122,6 +3130,7 @@ export type ResolversTypes = { ServerInfoUpdateInput: ServerInfoUpdateInput; ServerInvite: ResolverTypeWrapper; ServerInviteCreateInput: ServerInviteCreateInput; + ServerMigration: ResolverTypeWrapper; ServerRole: ServerRole; ServerRoleItem: ResolverTypeWrapper; ServerStatistics: ResolverTypeWrapper; @@ -3289,6 +3298,7 @@ export type ResolversParentTypes = { ServerInfoUpdateInput: ServerInfoUpdateInput; ServerInvite: ServerInviteGraphQLReturnType; ServerInviteCreateInput: ServerInviteCreateInput; + ServerMigration: ServerMigration; ServerRoleItem: ServerRoleItem; ServerStatistics: GraphQLEmptyReturn; ServerStats: ServerStats; @@ -4071,6 +4081,7 @@ export type ServerInfoResolvers, ParentType, ContextType>; guestModeEnabled?: Resolver; inviteOnly?: Resolver, ParentType, ContextType>; + migration?: Resolver, ParentType, ContextType>; name?: Resolver; roles?: Resolver, ParentType, ContextType>; scopes?: Resolver, ParentType, ContextType>; @@ -4087,6 +4098,12 @@ export type ServerInviteResolvers; }; +export type ServerMigrationResolvers = { + movedFrom?: Resolver, ParentType, ContextType>; + movedTo?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type ServerRoleItemResolvers = { id?: Resolver; title?: Resolver; @@ -4411,6 +4428,7 @@ export type Resolvers = { ServerAppListItem?: ServerAppListItemResolvers; ServerInfo?: ServerInfoResolvers; ServerInvite?: ServerInviteResolvers; + ServerMigration?: ServerMigrationResolvers; ServerRoleItem?: ServerRoleItemResolvers; ServerStatistics?: ServerStatisticsResolvers; ServerStats?: ServerStatsResolvers; diff --git a/packages/server/modules/core/helpers/types.ts b/packages/server/modules/core/helpers/types.ts index f2b2d577b..04f783eba 100644 --- a/packages/server/modules/core/helpers/types.ts +++ b/packages/server/modules/core/helpers/types.ts @@ -82,6 +82,7 @@ export type ServerInfo = ServerConfigRecord & { * Dynamically resolved from env vars */ version: string + migration?: { movedFrom?: string; movedTo?: string } } export type CommitRecord = { diff --git a/packages/server/modules/core/services/generic.js b/packages/server/modules/core/services/generic.js index e472151ef..b9fcc451e 100644 --- a/packages/server/modules/core/services/generic.js +++ b/packages/server/modules/core/services/generic.js @@ -1,5 +1,11 @@ 'use strict' const knex = require('@/db/knex') +const { + getServerVersion, + getServerOrigin, + getServerMovedTo, + getServerMovedFrom +} = require('@/modules/shared/helpers/envHelper') const Roles = () => knex('user_roles') const Scopes = () => knex('scopes') @@ -11,8 +17,11 @@ module.exports = { */ async getServerInfo() { const serverInfo = await Info().select('*').first() - serverInfo.version = process.env.SPECKLE_SERVER_VERSION || 'dev' - serverInfo.canonicalUrl = process.env.CANONICAL_URL || '127.0.0.1' + serverInfo.version = getServerVersion() + serverInfo.canonicalUrl = getServerOrigin() + const movedTo = getServerMovedTo() + const movedFrom = getServerMovedFrom() + if (movedTo || movedFrom) serverInfo.migration = { movedTo, movedFrom } return serverInfo }, diff --git a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts index 348c0cbc1..18e6c952b 100644 --- a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts +++ b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts @@ -2079,6 +2079,8 @@ export type ServerInfo = { description?: Maybe; guestModeEnabled: Scalars['Boolean']; inviteOnly?: Maybe; + /** Server relocation / migration info */ + migration?: Maybe; name: Scalars['String']; /** @deprecated Use role constants from the @speckle/shared npm package instead */ roles: Array; @@ -2112,6 +2114,12 @@ export type ServerInviteCreateInput = { serverRole?: InputMaybe; }; +export type ServerMigration = { + __typename?: 'ServerMigration'; + movedFrom?: Maybe; + movedTo?: Maybe; +}; + export enum ServerRole { ServerAdmin = 'SERVER_ADMIN', ServerArchivedUser = 'SERVER_ARCHIVED_USER', diff --git a/packages/server/modules/shared/helpers/envHelper.ts b/packages/server/modules/shared/helpers/envHelper.ts index d930a784f..3b93c3992 100644 --- a/packages/server/modules/shared/helpers/envHelper.ts +++ b/packages/server/modules/shared/helpers/envHelper.ts @@ -160,6 +160,28 @@ export function isSSLServer() { return /^https:\/\//.test(getBaseUrl()) } +function parseUrlVar(value: string, name: string) { + try { + return new URL(value) + } catch (err: unknown) { + if (err instanceof TypeError && err.message === 'Invalid URL') + throw new MisconfiguredEnvironmentError(`${name} has to be a valid URL`) + throw err + } +} + +export function getServerMovedFrom() { + const value = process.env.MIGRATION_SERVER_MOVED_FROM + if (!value) return value + return parseUrlVar(value, 'MIGRATION_SERVER_MOVED_FROM') +} + +export function getServerMovedTo() { + const value = process.env.MIGRATION_SERVER_MOVED_TO + if (!value) return value + return parseUrlVar(value, 'MIGRATION_SERVER_MOVED_TO') +} + export function adminOverrideEnabled() { return process.env.ADMIN_OVERRIDE_ENABLED === 'true' } diff --git a/packages/server/test/graphql/generated/graphql.ts b/packages/server/test/graphql/generated/graphql.ts index 756c48b06..eea51a2f5 100644 --- a/packages/server/test/graphql/generated/graphql.ts +++ b/packages/server/test/graphql/generated/graphql.ts @@ -2080,6 +2080,8 @@ export type ServerInfo = { description?: Maybe; guestModeEnabled: Scalars['Boolean']; inviteOnly?: Maybe; + /** Server relocation / migration info */ + migration?: Maybe; name: Scalars['String']; /** @deprecated Use role constants from the @speckle/shared npm package instead */ roles: Array; @@ -2113,6 +2115,12 @@ export type ServerInviteCreateInput = { serverRole?: InputMaybe; }; +export type ServerMigration = { + __typename?: 'ServerMigration'; + movedFrom?: Maybe; + movedTo?: Maybe; +}; + export enum ServerRole { ServerAdmin = 'SERVER_ADMIN', ServerArchivedUser = 'SERVER_ARCHIVED_USER', diff --git a/utils/helm/speckle-server/templates/server/deployment.yml b/utils/helm/speckle-server/templates/server/deployment.yml index 704817189..833409f1a 100644 --- a/utils/helm/speckle-server/templates/server/deployment.yml +++ b/utils/helm/speckle-server/templates/server/deployment.yml @@ -131,6 +131,17 @@ spec: - name: SPECKLE_AUTOMATE_URL value: {{ .Values.server.speckleAutomateUrl }} + + {{- if .Values.server.migration.movedFrom }} + - name: MIGRATION_SERVER_MOVED_FROM + value: {{ .Values.server.migration.movedFrom }} + {{- end }} + + {{- if .Values.server.migration.movedTo }} + - name: MIGRATION_SERVER_MOVED_TO + value: {{ .Values.server.migration.movedTo }} + {{- end }} + # *** Redis *** - name: REDIS_URL valueFrom: diff --git a/utils/helm/speckle-server/values.schema.json b/utils/helm/speckle-server/values.schema.json index a7006c197..49174661d 100644 --- a/utils/helm/speckle-server/values.schema.json +++ b/utils/helm/speckle-server/values.schema.json @@ -983,6 +983,21 @@ } } }, + "migration": { + "type": "object", + "properties": { + "movedFrom": { + "type": "string", + "description": "Indicate the URL where the server moved from", + "default": "" + }, + "movedTo": { + "type": "string", + "description": "Indicate the URL where the server moved to", + "default": "" + } + } + }, "monitoring": { "type": "object", "properties": { diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index a8939d845..7557d02b9 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -647,6 +647,12 @@ server: ## @param server.fileUploads.enabled If enabled, file uploads on the server will be flagged as enabled enabled: true + migration: + ## @param server.migration.movedFrom Indicate the URL where the server moved from + movedFrom: '' + ## @param server.migration.movedTo Indicate the URL where the server moved to + movedTo: '' + monitoring: apollo: ## @param server.monitoring.apollo.enabled (Optional) If enabled, exports metrics from the GraphQL API to Apollo Graphql Studio.