diff --git a/.github/workflows/server-ci-enterprise.yaml b/.github/workflows/server-ci-enterprise.yaml new file mode 100644 index 0000000000000..030514e37b465 --- /dev/null +++ b/.github/workflows/server-ci-enterprise.yaml @@ -0,0 +1,158 @@ +name: Server CI Enterprise + +on: + workflow_dispatch: + push: + branches: + - release-* + tags: + - v* + +concurrency: + group: mattermost-enterprise-linux-${{ github.ref_name }} + cancel-in-progress: true + +jobs: + build-server: + runs-on: ubuntu-latest + defaults: + run: + working-directory: server + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: npm + cache-dependency-path: webapp/package-lock.json + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: server/.go-version + cache-dependency-path: | + server/go.mod + server/public/go.sum + - name: Configure Enterprise edition + run: | + mkdir -p ../enterprise-dir/imports ../enterprise-dir/cloud/config + cp enterprise/placeholder.go ../enterprise-dir/imports/imports.go + cp enterprise/LICENSE ../enterprise-dir/ENTERPRISE-EDITION-LICENSE.txt + touch ../enterprise-dir/cloud/config/cloud_defaults.json + - name: Build + env: + BUILD_NUMBER: ${{ github.ref_name }}-${{ github.run_number }} + BUILD_ENTERPRISE_DIR: ../enterprise-dir + run: | + make config-reset + make build-cmd + make package + - name: Persist dist artifacts + uses: actions/upload-artifact@v4 + with: + name: server-dist-artifact + path: server/dist/ + retention-days: 14 + + - name: Create GitHub Release + uses: actions/github-script@v7 + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + env: + TAG_NAME: ${{ github.ref_name }} + RELEASE_NAME: ${{ github.ref_name }} + BODY: "Full Changelog: https://github.com/${{ github.repository }}/commits/${{ github.ref_name }}" + ASSETS: >- + server/dist/mattermost-enterprise-linux-amd64.tar.gz, + server/dist/mattermost-enterprise-linux-arm64.tar.gz, + server/dist/mattermost-enterprise-osx-amd64.tar.gz, + server/dist/mattermost-enterprise-osx-arm64.tar.gz, + server/dist/mattermost-enterprise-windows-amd64.zip + with: + script: | + const fs = require('fs'); + const path = require('path'); + const { TAG_NAME, RELEASE_NAME, BODY, ASSETS } = process.env; + const assets = ASSETS.split(','); + let uploadUrl = ''; + try { + const response = await github.rest.repos.createRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: TAG_NAME, + name: RELEASE_NAME, + body: BODY, + draft: false, + prerelease: false, + }); + console.log(`Release created: ${response.data.html_url}`); + uploadUrl = response.data.upload_url; + } catch (error) { + if (error.response && error.response.status === 422) { + core.warning(`Release already exists for tag ${TAG_NAME}. Skipping release creation and asset uploads.`); + return; + } else { + core.setFailed(`Error creating release: ${error.message}`); + return; + } + } + if (uploadUrl) { + for (const asset of assets) { + const assetPath = path.join(process.env.GITHUB_WORKSPACE, asset.trim()); + const contentType = asset.endsWith('.zip') ? 'application/zip' : 'application/gzip'; + const filename = path.basename(assetPath); + console.log(`Uploading asset: ${filename}`); + try { + const uploadResponse = await github.rest.repos.uploadReleaseAsset({ + url: uploadUrl, + headers: { + 'content-type': contentType, + 'content-length': fs.statSync(assetPath).size + }, + name: filename, + data: fs.readFileSync(assetPath), + }); + console.log(`Asset uploaded: ${uploadResponse.data.browser_download_url}`); + } catch (uploadError) { + core.error(`Error uploading asset: ${filename}, ${uploadError.message}`); + } + } + } + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker image + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/tuxity/mattermost-enterprise-edition + flavor: | + latest=false + tags: | + type=semver,pattern={{version}},value=${{ github.ref_name }} + type=raw,value=latest,enable=${{ !startsWith(github.ref, 'refs/tags/v') }} + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: server/build + build-contexts: + dist=server/dist + build-args: + MM_PACKAGE=file:///tmp/mattermost-enterprise-linux-amd64.tar.gz + platforms: linux/amd64 + push: ${{ startsWith(github.ref, 'refs/tags/v') }} + tags: ${{ steps.meta.outputs.tags }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/server/Makefile b/server/Makefile index e58c53e9c400c..39620eb6b2627 100644 --- a/server/Makefile +++ b/server/Makefile @@ -392,7 +392,7 @@ ifneq ($(IGNORE_GO_WORK_IF_EXISTS),true) $(GO) work use . $(GO) work use ./public ifeq ($(BUILD_ENTERPRISE_READY),true) - $(GO) work use ../../enterprise + $(GO) work use $(BUILD_ENTERPRISE_DIR) endif endif diff --git a/server/build/Dockerfile b/server/build/Dockerfile index 6a4a0af4dbbb5..8eaaaa7128a1e 100644 --- a/server/build/Dockerfile +++ b/server/build/Dockerfile @@ -13,6 +13,8 @@ ARG PGID=2000 # i.e. https://releases.mattermost.com/9.7.1/mattermost-9.7.1-linux-amd64.tar.gz ARG MM_PACKAGE="https://latest.mattermost.com/mattermost-enterprise-linux" +COPY --from=dist mattermost-* /tmp + # # Install needed packages and indirect dependencies RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ diff --git a/server/channels/app/platform/license.go b/server/channels/app/platform/license.go index d9b5fa0cded5e..3bac0976d2d4c 100644 --- a/server/channels/app/platform/license.go +++ b/server/channels/app/platform/license.go @@ -16,9 +16,11 @@ import ( "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/mlog" - "github.com/mattermost/mattermost/server/public/shared/request" + + //"github.com/mattermost/mattermost/server/public/shared/request" "github.com/mattermost/mattermost/server/v8/channels/jobs" - "github.com/mattermost/mattermost/server/v8/channels/store/sqlstore" + + //"github.com/mattermost/mattermost/server/v8/channels/store/sqlstore" "github.com/mattermost/mattermost/server/v8/channels/utils" "github.com/mattermost/mattermost/server/v8/einterfaces" ) @@ -49,7 +51,7 @@ func (ps *PlatformService) License() *model.License { } func (ps *PlatformService) LoadLicense() { - c := request.EmptyContext(ps.logger) + //c := request.EmptyContext(ps.logger) // ENV var overrides all other sources of license. licenseStr := os.Getenv(LicenseEnv) @@ -82,37 +84,60 @@ func (ps *PlatformService) LoadLicense() { return } - licenseId := "" - props, nErr := ps.Store.System().Get() - if nErr == nil { - licenseId = props[model.SystemActiveLicenseId] - } + /* + licenseId := "" + props, nErr := ps.Store.System().Get() + if nErr == nil { + licenseId = props[model.SystemActiveLicenseId] + } - if !model.IsValidId(licenseId) { - // Lets attempt to load the file from disk since it was missing from the DB - license, licenseBytes, err := utils.GetAndValidateLicenseFileFromDisk(*ps.Config().ServiceSettings.LicenseFileLocation) - if err != nil { - ps.logger.Warn("Failed to get license from disk", mlog.Err(err)) - } else { - if _, err := ps.SaveLicense(licenseBytes); err != nil { - ps.logger.Error("Failed to save license key loaded from disk.", mlog.Err(err)) + if !model.IsValidId(licenseId) { + // Lets attempt to load the file from disk since it was missing from the DB + license, licenseBytes, err := utils.GetAndValidateLicenseFileFromDisk(*ps.Config().ServiceSettings.LicenseFileLocation) + if err != nil { + ps.logger.Warn("Failed to get license from disk", mlog.Err(err)) } else { - licenseId = license.Id + if _, err := ps.SaveLicense(licenseBytes); err != nil { + ps.logger.Error("Failed to save license key loaded from disk.", mlog.Err(err)) + } else { + licenseId = license.Id + } } } - } - - record, nErr := ps.Store.License().Get(sqlstore.RequestContextWithMaster(c), licenseId) - if nErr != nil { - ps.logger.Warn("License key from https://mattermost.com required to unlock enterprise features.", mlog.Err(nErr)) - ps.SetLicense(nil) - return - } - err := ps.ValidateAndSetLicenseBytes([]byte(record.Bytes)) - if err != nil { - ps.logger.Info("License key is invalid.") - } + record, nErr := ps.Store.License().Get(sqlstore.RequestContextWithMaster(c), licenseId) + if nErr != nil { + ps.logger.Warn("License key from https://mattermost.com required to unlock enterprise features.", mlog.Err(nErr)) + ps.SetLicense(nil) + return + } + */ + + f := model.Features{} + f.SetDefaults() + *f.Users = 9999 + *f.Elasticsearch = false + + ps.SetLicense(&model.License{ + Id: model.NewId(), + IssuedAt: 0, + ExpiresAt: 4102491600000, // 1st january 2100 in ms + Customer: &model.Customer{ + Name: "Mr Robot", + Email: "mrrobot@fsociety.com", + Company: "fsociety", + }, + Features: &f, + SkuName: "Enterprise", + SkuShortName: model.LicenseShortSkuEnterprise, + }) + + /* + err := ps.ValidateAndSetLicenseBytes([]byte(record.Bytes)) + if err != nil { + ps.logger.Info("License key is invalid.") + } + */ ps.logger.Info("License key is valid, unlocking enterprise features.") } diff --git a/server/enterprise/external_imports.go b/server/enterprise/external_imports.go index ae02565354a09..62a32b648da82 100644 --- a/server/enterprise/external_imports.go +++ b/server/enterprise/external_imports.go @@ -5,6 +5,7 @@ package enterprise +/* import ( // Needed to ensure the init() method in the EE gets run _ "github.com/mattermost/enterprise/account_migration" @@ -45,3 +46,4 @@ import ( // Needed to ensure the init() method in the EE gets run _ "github.com/mattermost/enterprise/outgoing_oauth_connections" ) +*/