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 }