github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/ci/packages/raspberry.go (about)

     1  /*
     2   * Copyright (C) 2019 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package packages
    19  
    20  import (
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  
    26  	"github.com/aws/aws-sdk-go-v2/aws"
    27  	"github.com/aws/aws-sdk-go-v2/service/s3/types"
    28  	"github.com/mholt/archiver/v3"
    29  	"github.com/mysteriumnetwork/go-ci/env"
    30  	"github.com/mysteriumnetwork/go-ci/job"
    31  	"github.com/mysteriumnetwork/go-ci/shell"
    32  	"github.com/mysteriumnetwork/node/ci/storage"
    33  	"github.com/mysteriumnetwork/node/ci/util/device"
    34  	"github.com/mysteriumnetwork/node/logconfig"
    35  	"github.com/pkg/errors"
    36  	"github.com/rs/zerolog/log"
    37  )
    38  
    39  const (
    40  	raspbianMountPoint = "/mnt/rpi"
    41  	setupDir           = "/home/myst-setup.tmp"
    42  	mountedSetupDir    = raspbianMountPoint + setupDir
    43  )
    44  
    45  // PackageLinuxRaspberryImage builds and stores raspberry image.
    46  // When bumping the raspios/raspbian version, use the following guide to resize image's filesystem
    47  // (so that it does not run out of space during the setup). I added +1G to the downloaded image.
    48  // https://wiki.debian.org/RaspberryPi/qemu-user-static
    49  func PackageLinuxRaspberryImage() error {
    50  	job.Precondition(func() bool {
    51  		pr, _ := env.IsPR()
    52  		fullBuild, _ := env.IsFullBuild()
    53  		return !pr || fullBuild
    54  	})
    55  	logconfig.Bootstrap()
    56  
    57  	if err := goGet("github.com/debber/debber-v0.3/cmd/debber"); err != nil {
    58  		return err
    59  	}
    60  	if err := shell.NewCmd("bin/build_xgo linux/arm").Run(); err != nil {
    61  		return err
    62  	}
    63  	if err := packageDebian("build/myst/myst_linux_arm", "armhf"); err != nil {
    64  		return err
    65  	}
    66  	if err := buildMystRaspbianImage(); err != nil {
    67  		return err
    68  	}
    69  	return env.IfRelease(storage.UploadArtifacts)
    70  }
    71  
    72  func buildMystRaspbianImage() error {
    73  	logconfig.Bootstrap()
    74  
    75  	imagePath, err := fetchRaspbianImage()
    76  	if err != nil {
    77  		return err
    78  	}
    79  	if err := configureRaspbianImage(imagePath); err != nil {
    80  		return err
    81  	}
    82  	if err := archiver.DefaultZip.Archive([]string{imagePath}, "build/package/mystberry.zip"); err != nil {
    83  		return err
    84  	}
    85  	if err := os.Remove(imagePath); err != nil {
    86  		return err
    87  	}
    88  	return nil
    89  }
    90  
    91  // configureRaspbianImage mounts given raspbian image, spawns a lightweight container via systemd-nspawn and configures it
    92  // see `setup.sh` for setup script executed in container
    93  func configureRaspbianImage(raspbianImagePath string) error {
    94  	envs := map[string]string{
    95  		"PATH":            "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
    96  		"DEBIAN_FRONTEND": "noninteractive",
    97  	}
    98  
    99  	if err := shell.NewCmd("sudo apt-get update").Run(); err != nil {
   100  		return err
   101  	}
   102  	if err := shell.NewCmd("sudo apt-get install -y qemu qemu-user-static binfmt-support systemd-container").RunWith(envs); err != nil {
   103  		return err
   104  	}
   105  	loopDevice, err := device.AttachLoop(raspbianImagePath)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	defer func() {
   110  		if err := device.DetachLoop(loopDevice); err != nil {
   111  			log.Warn().Err(err).Msg("")
   112  		}
   113  	}()
   114  
   115  	_ = shell.NewCmdf("sudo mkdir -p %s", raspbianMountPoint).Run()
   116  	if err := shell.NewCmdf("sudo mount --options rw %s %s", loopDevice+"p2", raspbianMountPoint).Run(); err != nil {
   117  		return err
   118  	}
   119  	_ = shell.NewCmdf("sudo mkdir -p %s", raspbianMountPoint+"/boot").Run()
   120  	if err := shell.NewCmdf("sudo mount --options rw %s %s", loopDevice+"p1", raspbianMountPoint+"/boot").Run(); err != nil {
   121  		return err
   122  	}
   123  	defer func() {
   124  		if err := shell.NewCmdf("sudo umount --recursive %s", raspbianMountPoint).Run(); err != nil {
   125  			log.Warn().Err(err).Msg("")
   126  		}
   127  	}()
   128  	err = shell.NewCmdf("sudo sed -i s/^/#/g %s", raspbianMountPoint+"/etc/ld.so.preload").Run()
   129  	if err != nil {
   130  		return err
   131  	}
   132  	defer func() {
   133  		err = shell.NewCmdf("sudo sed -i s/^#//g %s", raspbianMountPoint+"/etc/ld.so.preload").Run()
   134  		if err != nil {
   135  			log.Warn().Err(err).Msg("")
   136  		}
   137  	}()
   138  
   139  	if err := shell.NewCmdf("sudo cp /usr/bin/qemu-arm-static %s", raspbianMountPoint+"/usr/bin").Run(); err != nil {
   140  		return err
   141  	}
   142  
   143  	if err := shell.NewCmdf("sudo mkdir -p %s", mountedSetupDir).Run(); err != nil {
   144  		return err
   145  	}
   146  	if err := shell.NewCmdf("sudo cp build/package/myst_linux_armhf.deb %s", mountedSetupDir).Run(); err != nil {
   147  		return err
   148  	}
   149  	if err := shell.NewCmdf("sudo cp -r bin/package/raspberry/files/. %s", mountedSetupDir).Run(); err != nil {
   150  		return err
   151  	}
   152  	if err := shell.NewCmdf("sudo systemd-nspawn --directory=%s --chdir=%s bash -ev 0-setup-user.sh", raspbianMountPoint, setupDir).Run(); err != nil {
   153  		return err
   154  	}
   155  	if err := shell.NewCmdf("sudo systemd-nspawn --setenv=RELEASE_BUILD=%s --directory=%s --chdir=%s bash -ev 1-setup-node.sh", env.Str(env.TagBuild), raspbianMountPoint, setupDir).Run(); err != nil {
   156  		return err
   157  	}
   158  	if err := shell.NewCmdf("sudo rm -r %s", mountedSetupDir).Run(); err != nil {
   159  		return err
   160  	}
   161  	return nil
   162  }
   163  
   164  func fetchRaspbianImage() (filename string, err error) {
   165  	storageClient, err := storage.NewClient()
   166  	if err != nil {
   167  		return "", err
   168  	}
   169  
   170  	log.Info().Msg("Looking up Raspbian image file")
   171  	localRaspbianZip, err := storageClient.GetCacheableFile("raspbian", func(object types.Object) bool {
   172  		return strings.Contains(aws.ToString(object.Key), "-raspbian-buster-lite")
   173  	})
   174  	if err != nil {
   175  		return "", fmt.Errorf("failed to fetch raspbian image: %w", err)
   176  	}
   177  
   178  	localRaspbianZipDir, localRaspbianZipFilename := filepath.Split(localRaspbianZip)
   179  	localRaspbianImgDir := filepath.Join(localRaspbianZipDir, localRaspbianZipFilename[0:len(localRaspbianZipFilename)-4])
   180  
   181  	log.Info().Msg("Extracting raspbian image to: " + localRaspbianImgDir)
   182  	err = os.RemoveAll(localRaspbianImgDir)
   183  	if err != nil {
   184  		return "", err
   185  	}
   186  
   187  	zip := archiver.NewZip()
   188  	zip.OverwriteExisting = true
   189  	err = zip.Unarchive(localRaspbianZip, localRaspbianImgDir)
   190  	if err != nil {
   191  		return "", err
   192  	}
   193  
   194  	extractedFiles, err := os.ReadDir(localRaspbianImgDir)
   195  	if err != nil {
   196  		return "", err
   197  	}
   198  
   199  	var localRaspbianImg string
   200  	for _, f := range extractedFiles {
   201  		if strings.Contains(f.Name(), ".img") {
   202  			localRaspbianImg = filepath.Join(localRaspbianImgDir, f.Name())
   203  			break
   204  		}
   205  	}
   206  	if localRaspbianImg == "" {
   207  		return "", errors.New("could not find img file in raspbian archive")
   208  	}
   209  	return localRaspbianImg, nil
   210  }