github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/services/upgrader/upgrader_linux.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package upgrader
     5  
     6  import (
     7  	"archive/tar"
     8  	"bytes"
     9  	"compress/gzip"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"net/http"
    14  	"os"
    15  	"os/user"
    16  	"path"
    17  	"runtime"
    18  	"strconv"
    19  	"strings"
    20  	"sync/atomic"
    21  	"syscall"
    22  
    23  	"github.com/pkg/errors"
    24  	"golang.org/x/crypto/openpgp"
    25  
    26  	"github.com/masterhung0112/hk_server/v5/model"
    27  	"github.com/masterhung0112/hk_server/v5/shared/mlog"
    28  )
    29  
    30  const mattermostBuildPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
    31  
    32  mQENBFjZQxwBCAC6kNn3zDlq/aY83M9V7MHVPoK2jnZ3BfH7sA+ibQXsijCkPSR4
    33  5bCUJ9qVA4XKGK+cpO9vkolSNs10igCaaemaUZNB6ksu3gT737/SZcCAfRO+cLX7
    34  Q2la+jwTvu1YeT/M5xDZ1KHTFxsGskeIenz2rZHeuZwBl9qep34QszWtRX40eRts
    35  fl6WltLrepiExTp6NMZ50k+Em4JGM6CWBMo22ucy0jYjZXO5hEGb3o6NGiG+Dx2z
    36  b2J78LksCKGsSrn0F1rLJeA933bFL4g9ozv9asBlzmpgG77ESg6YE1N/Rh7WDzVA
    37  prIR0MuB5JjElASw5LDVxDV6RZsxEVQr7ETLABEBAAG0KU1hdHRlcm1vc3QgQnVp
    38  bGQgPGRldi1vcHNAbWF0dGVybW9zdC5jb20+iQFUBBMBCAA+AhsDBQsJCAcCBhUI
    39  CQoLAgQWAgMBAh4BAheAFiEEobMdRvDzoQsCzy1E+PLDF0R3SygFAl6HYr0FCQlw
    40  hqEACgkQ+PLDF0R3SyheNQgAnkiT2vFMCtU5FmC16HVYXzDpYMtdCQPh/gmeEkiI
    41  80rFRg/cn6f0BNnaTfDu6r6cepmhLNpDAowjQ7uBnv8fL2dzCydIGFv2r7FfmcOJ
    42  zhEQ3zXPwP6mYlxPCCgxAozsLv9Yv41KGCHIlzYwkAazc0BhpAW/h8L3VGkE+b+g
    43  x6lKVoufm4rKnT49Dgly6fVOxuR/BqZo87B5jksV3izLTHt5hiY8Pc5GW8WwO/tr
    44  pNAw+6HRXq1Dr/JRz5PIOr5KP5tVLBed4IteZ1xaTRd4++07ZbiZjhXY8WKpVp3y
    45  iN7Om24jQpxbJI9+KKJ3+yhcwhr8/PJ8ZVuhJo3BNv1PcQ==
    46  =9Qk8
    47  -----END PGP PUBLIC KEY BLOCK-----`
    48  
    49  var upgradePercentage int64
    50  var upgradeError error
    51  var upgrading int32
    52  
    53  type writeCounter struct {
    54  	total  int64
    55  	readed int64
    56  }
    57  
    58  func (wc *writeCounter) Write(p []byte) (int, error) {
    59  	n := len(p)
    60  	wc.readed += int64(n)
    61  	percentage := (wc.readed * 100) / wc.total
    62  	if percentage == 0 {
    63  		upgradePercentage = 1
    64  	} else if percentage == 100 {
    65  		upgradePercentage = 99
    66  	} else {
    67  		upgradePercentage = percentage
    68  	}
    69  	return n, nil
    70  }
    71  
    72  func getCurrentVersionTgzUrl() string {
    73  	version := model.CurrentVersion
    74  	if strings.HasPrefix(model.BuildNumber, version+"-rc") {
    75  		version = model.BuildNumber
    76  	}
    77  
    78  	return "https://releases.mattermost.com/" + version + "/mattermost-" + version + "-linux-amd64.tar.gz"
    79  }
    80  
    81  func verifySignature(filename string, sigfilename string, publicKey string) error {
    82  	keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(publicKey)))
    83  	if err != nil {
    84  		mlog.Debug("Unable to load the public key to verify the file signature", mlog.Err(err))
    85  		return NewInvalidSignature()
    86  	}
    87  
    88  	mattermost_tar, err := os.Open(filename)
    89  	if err != nil {
    90  		mlog.Debug("Unable to open the Mattermost .tar file to verify the file signature", mlog.Err(err))
    91  		return NewInvalidSignature()
    92  	}
    93  
    94  	signature, err := os.Open(sigfilename)
    95  	if err != nil {
    96  		mlog.Debug("Unable to open the Mattermost .sig file verify the file signature", mlog.Err(err))
    97  		return NewInvalidSignature()
    98  	}
    99  
   100  	_, err = openpgp.CheckDetachedSignature(keyring, mattermost_tar, signature)
   101  	if err != nil {
   102  		mlog.Debug("Unable to verify the Mattermost file signature", mlog.Err(err))
   103  		return NewInvalidSignature()
   104  	}
   105  	return nil
   106  }
   107  
   108  func canIWriteTheExecutable() error {
   109  	executablePath, err := os.Executable()
   110  	if err != nil {
   111  		return errors.New("error getting the path of the executable")
   112  	}
   113  	executableInfo, err := os.Stat(path.Dir(executablePath))
   114  	if err != nil {
   115  		return errors.New("error getting the executable info")
   116  	}
   117  	stat, ok := executableInfo.Sys().(*syscall.Stat_t)
   118  	if !ok {
   119  		return errors.New("error getting the executable info")
   120  	}
   121  	fileUID := int(stat.Uid)
   122  	fileUser, err := user.LookupId(strconv.Itoa(fileUID))
   123  	if err != nil {
   124  		return errors.New("error getting the executable info")
   125  	}
   126  
   127  	mattermostUID := os.Getuid()
   128  	mattermostUser, err := user.LookupId(strconv.Itoa(mattermostUID))
   129  	if err != nil {
   130  		return errors.New("error getting the executable info")
   131  	}
   132  
   133  	mode := executableInfo.Mode()
   134  	if fileUID != mattermostUID && mode&(1<<1) == 0 && mode&(1<<7) == 0 {
   135  		return NewInvalidPermissions("invalid-user-and-permission", path.Dir(executablePath), mattermostUser.Username, fileUser.Username)
   136  	}
   137  
   138  	if fileUID != mattermostUID && mode&(1<<1) == 0 && mode&(1<<7) != 0 {
   139  		return NewInvalidPermissions("invalid-user", path.Dir(executablePath), mattermostUser.Username, fileUser.Username)
   140  	}
   141  
   142  	if fileUID == mattermostUID && mode&(1<<7) == 0 {
   143  		return NewInvalidPermissions("invalid-permission", path.Dir(executablePath), mattermostUser.Username, fileUser.Username)
   144  	}
   145  	return nil
   146  }
   147  
   148  func canIUpgrade() error {
   149  	if runtime.GOARCH != "amd64" {
   150  		return NewInvalidArch()
   151  	}
   152  	if runtime.GOOS != "linux" {
   153  		return NewInvalidArch()
   154  	}
   155  	return canIWriteTheExecutable()
   156  }
   157  
   158  func CanIUpgradeToE0() error {
   159  	if err := canIUpgrade(); err != nil {
   160  		return errors.Wrap(err, "unable to upgrade from TE to E0")
   161  	}
   162  	if model.BuildEnterpriseReady == "true" {
   163  		mlog.Warn("Unable to upgrade from TE to E0. The server is already running E0.")
   164  		return errors.New("you cannot upgrade your server from TE to E0 because you are already running Mattermost Enterprise Edition")
   165  	}
   166  	return nil
   167  }
   168  
   169  func UpgradeToE0() error {
   170  	if !atomic.CompareAndSwapInt32(&upgrading, 0, 1) {
   171  		mlog.Warn("Trying to upgrade while another upgrade is running")
   172  		return errors.New("another upgrade is already running")
   173  	}
   174  	defer atomic.CompareAndSwapInt32(&upgrading, 1, 0)
   175  
   176  	upgradePercentage = 1
   177  	upgradeError = nil
   178  
   179  	executablePath, err := os.Executable()
   180  	if err != nil {
   181  		upgradePercentage = 0
   182  		upgradeError = errors.New("error getting the executable path")
   183  		mlog.Error("Unable to get the path of the Mattermost executable", mlog.Err(err))
   184  		return err
   185  	}
   186  
   187  	filename, err := download(getCurrentVersionTgzUrl(), 1024*1024*300)
   188  	if err != nil {
   189  		if filename != "" {
   190  			os.Remove(filename)
   191  		}
   192  		upgradeError = fmt.Errorf("error downloading the new HungKnow server binary file (percentage: %d)", upgradePercentage)
   193  		mlog.Error("Unable to download the HungKnow server binary file", mlog.Int64("percentage", upgradePercentage), mlog.String("url", getCurrentVersionTgzUrl()), mlog.Err(err))
   194  		upgradePercentage = 0
   195  		return err
   196  	}
   197  	defer os.Remove(filename)
   198  	sigfilename, err := download(getCurrentVersionTgzUrl()+".sig", 1024)
   199  	if err != nil {
   200  		if sigfilename != "" {
   201  			os.Remove(sigfilename)
   202  		}
   203  		upgradeError = errors.New("error downloading the signature file of the new server")
   204  		mlog.Error("Unable to download the signature file of the new HungKnow server ", mlog.String("url", getCurrentVersionTgzUrl()+".sig"), mlog.Err(err))
   205  		upgradePercentage = 0
   206  		return err
   207  	}
   208  	defer os.Remove(sigfilename)
   209  
   210  	err = verifySignature(filename, sigfilename, mattermostBuildPublicKey)
   211  	if err != nil {
   212  		upgradePercentage = 0
   213  		upgradeError = errors.New("unable to verify the signature of the downloaded file")
   214  		mlog.Error("Unable to verify the signature of the downloaded file", mlog.Err(err))
   215  		return err
   216  	}
   217  
   218  	err = extractBinary(executablePath, filename)
   219  	if err != nil {
   220  		upgradePercentage = 0
   221  		upgradeError = err
   222  		mlog.Error("Unable to extract the binary from the downloaded file", mlog.Err(err))
   223  		return err
   224  	}
   225  	upgradePercentage = 100
   226  	return nil
   227  }
   228  
   229  func UpgradeToE0Status() (int64, error) {
   230  	return upgradePercentage, upgradeError
   231  }
   232  
   233  func download(url string, limit int64) (string, error) {
   234  	resp, err := http.Get(url)
   235  	if err != nil {
   236  		return "", err
   237  	}
   238  	defer resp.Body.Close()
   239  
   240  	out, err := ioutil.TempFile("", "*_mattermost.tar.gz")
   241  	if err != nil {
   242  		return "", err
   243  	}
   244  	defer out.Close()
   245  
   246  	counter := &writeCounter{total: resp.ContentLength}
   247  	_, err = io.Copy(out, io.TeeReader(&io.LimitedReader{R: resp.Body, N: limit}, counter))
   248  	return out.Name(), err
   249  }
   250  
   251  func getFilePermissionsOrDefault(filename string, def os.FileMode) os.FileMode {
   252  	file, err := os.Open(filename)
   253  	if err != nil {
   254  		mlog.Warn("Unable to get the file permissions", mlog.String("filename", filename), mlog.Err(err))
   255  		return def
   256  	}
   257  	defer file.Close()
   258  
   259  	fileStats, err := file.Stat()
   260  	if err != nil {
   261  		mlog.Warn("Unable to get the file permissions", mlog.String("filename", filename), mlog.Err(err))
   262  		return def
   263  	}
   264  	return fileStats.Mode()
   265  }
   266  
   267  func extractBinary(executablePath string, filename string) error {
   268  	gzipStream, err := os.Open(filename)
   269  	if err != nil {
   270  		return err
   271  	}
   272  
   273  	uncompressedStream, err := gzip.NewReader(gzipStream)
   274  	if err != nil {
   275  		return err
   276  	}
   277  
   278  	tarReader := tar.NewReader(uncompressedStream)
   279  
   280  	for {
   281  		header, err := tarReader.Next()
   282  
   283  		if err == io.EOF {
   284  			return errors.New("unable to find the Mattermost binary in the downloaded version")
   285  		}
   286  
   287  		if err != nil {
   288  			return err
   289  		}
   290  
   291  		if header.Typeflag == tar.TypeReg && header.Name == "mattermost/bin/mattermost" {
   292  			permissions := getFilePermissionsOrDefault(executablePath, 0755)
   293  			tmpFile, err := ioutil.TempFile(path.Dir(executablePath), "*")
   294  			if err != nil {
   295  				return err
   296  			}
   297  			tmpFileName := tmpFile.Name()
   298  			os.Remove(tmpFileName)
   299  			err = os.Rename(executablePath, tmpFileName)
   300  			if err != nil {
   301  				return err
   302  			}
   303  			outFile, err := os.Create(executablePath)
   304  			if err != nil {
   305  				err2 := os.Rename(tmpFileName, executablePath)
   306  				if err2 != nil {
   307  					mlog.Critical("Unable to restore the backup of the executable file. Restore the executable file manually.")
   308  					return errors.Wrap(err2, "critical error: unable to upgrade the binary or restore the old binary version. Please restore it manually")
   309  				}
   310  				return err
   311  			}
   312  			defer outFile.Close()
   313  			if _, err = io.Copy(outFile, tarReader); err != nil {
   314  				err2 := os.Remove(executablePath)
   315  				if err2 != nil {
   316  					mlog.Critical("Unable to restore the backup of the executable file. Restore the executable file manually.")
   317  					return errors.Wrap(err2, "critical error: unable to upgrade the binary or restore the old binary version. Please restore it manually")
   318  				}
   319  
   320  				err2 = os.Rename(tmpFileName, executablePath)
   321  				if err2 != nil {
   322  					mlog.Critical("Unable to restore the backup of the executable file. Restore the executable file manually.")
   323  					return errors.Wrap(err2, "critical error: unable to upgrade the binary or restore the old binary version. Please restore it manually")
   324  				}
   325  				return err
   326  			}
   327  			err = os.Remove(tmpFileName)
   328  			if err != nil {
   329  				mlog.Warn("Unable to clean up the binary backup file.", mlog.Err(err))
   330  			}
   331  			err = os.Chmod(executablePath, permissions)
   332  			if err != nil {
   333  				mlog.Warn("Unable to set the correct permissions for the file.", mlog.Err(err))
   334  			}
   335  			break
   336  		}
   337  	}
   338  	return nil
   339  }