├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ ├── docs.yml │ ├── release.yml │ └── vendored-release.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── api ├── arch.go ├── download.go ├── finalize-scopes.go ├── go.mod └── structs.go ├── cmd ├── build.go ├── compile.go ├── root.go └── test.go ├── core ├── build.go ├── compile.go ├── finalize.go ├── loader.go ├── plugins.in ├── resolver_test.go ├── shell.go └── structs.go ├── docs ├── articles │ └── en │ │ ├── built-in-modules.md │ │ ├── contributing.md │ │ ├── getting-started.md │ │ ├── github-action.md │ │ ├── making-plugin.md │ │ ├── project-structure.md │ │ ├── recipe-structure.md │ │ ├── use-modules.md │ │ └── vscode-extension.md └── uploads │ └── vib-recipe-structure.png ├── example ├── Containerfile ├── example.yml ├── inst │ └── 00-test.inst └── modules │ ├── 00-net.yml │ └── 10-editor.yml ├── finalize-plugins ├── Makefile ├── genimage.go ├── shell-final.go ├── sysext.go └── systemd-repart.go ├── go.mod ├── go.sum ├── logo ├── png │ ├── full-mono-dark.png │ ├── full-mono-light.png │ ├── full.png │ ├── icon-mono-dark.png │ ├── icon-mono-light.png │ └── icon.png └── svg │ ├── full-mono-dark.svg │ ├── full-mono-light.svg │ ├── full.svg │ ├── icon-mono-dark.svg │ ├── icon-mono-light.svg │ └── icon.svg ├── main.go ├── plugins ├── Makefile ├── apt.go ├── cmake.go ├── dpkg-buildpackage.go ├── flatpak.go ├── go.go ├── make.go ├── meson.go └── shim.go └── set_new_version.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | # Ref: https://git-scm.com/docs/gitattributes 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | - package-ecosystem: "gomod" 8 | directory: "/" 9 | schedule: 10 | interval: "monthly" 11 | - package-ecosystem: "npm" 12 | directory: "docs/website/" 13 | schedule: 14 | interval: "monthly" 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Set up Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version-file: go.mod 22 | 23 | - name: Install Build Dependencies 24 | run: | 25 | sudo apt-get update 26 | sudo apt-get install -y libbtrfs-dev make gcc gcc-aarch64-linux-gnu 27 | 28 | - name: Build vib 29 | run: | 30 | go get ./... 31 | go get github.com/ebitengine/purego 32 | make BINARY_NAME=vib-amd64 build 33 | mv build/vib-amd64 ./ 34 | make clean 35 | GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc make BINARY_NAME=vib-arm64 build 36 | mv build/vib-arm64 ./ 37 | 38 | - name: Build plugins 39 | run: | 40 | go get ./... 41 | make build-plugins 42 | tar czvf plugins-amd64.tar.gz build/plugins 43 | make clean 44 | GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc make build-plugins 45 | tar czvf plugins-arm64.tar.gz build/plugins 46 | 47 | - uses: actions/upload-artifact@v4 48 | with: 49 | name: Vib 50 | path: | 51 | vib* 52 | plugins*.tar.gz 53 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | 14 | - name: Set up Go 15 | uses: actions/setup-go@v5 16 | with: 17 | go-version-file: go.mod 18 | 19 | - name: Install pallas 20 | run: | 21 | curl -L -o pallas https://github.com/Vanilla-OS/Pallas/releases/download/continuous/pallas 22 | chmod +x pallas 23 | 24 | - name: Generate Docs 25 | run: ./pallas 26 | 27 | - name: Setup Pages 28 | id: pages 29 | uses: actions/configure-pages@v5 30 | 31 | - name: Build with Jekyll 32 | uses: actions/jekyll-build-pages@v1 33 | with: 34 | source: ./dist 35 | destination: ./_site 36 | 37 | - name: Upload artifact 38 | uses: actions/upload-pages-artifact@v3 39 | 40 | deploy: 41 | permissions: 42 | contents: read 43 | pages: write 44 | id-token: write 45 | environment: 46 | name: github-pages 47 | url: ${{steps.deployment.outputs.page_url}} 48 | runs-on: ubuntu-latest 49 | needs: build 50 | steps: 51 | - name: Deploy to GitHub Pages 52 | id: deployment 53 | uses: actions/deploy-pages@v4 54 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write # to upload assets to releases 13 | attestations: write # to upload assets attestation for build provenance 14 | id-token: write # grant additional permission to attestation action to mint the OIDC token permission 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version-file: go.mod 23 | 24 | - name: Install Build Dependencies 25 | run: | 26 | sudo apt-get update 27 | sudo apt-get install -y libbtrfs-dev make gcc gcc-aarch64-linux-gnu 28 | 29 | - name: Build 30 | run: | 31 | ./set_new_version.sh ${{ github.ref_name }} 32 | go get ./... 33 | go get github.com/ebitengine/purego 34 | make BINARY_NAME=vib-amd64 build 35 | mv build/vib-amd64 ./ 36 | make clean 37 | GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc make BINARY_NAME=vib-arm64 build 38 | mv build/vib-arm64 ./ 39 | 40 | - name: Build plugins 41 | run: | 42 | go get ./... 43 | make build-plugins 44 | tar czvf plugins-amd64.tar.gz build/plugins 45 | make clean 46 | GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc make build-plugins 47 | tar czvf plugins-arm64.tar.gz build/plugins 48 | 49 | - name: Upload a Release Asset 50 | if: github.repository == 'Vanilla-OS/Vib' 51 | uses: softprops/action-gh-release@v2 52 | with: 53 | files: | 54 | vib* 55 | plugins*.tar.gz 56 | 57 | - name: Attest generated files 58 | if: github.repository == 'Vanilla-OS/Vib' 59 | id: attest 60 | uses: actions/attest-build-provenance@v2 61 | with: 62 | subject-path: 'vib*, plugins*.tar.gz' 63 | -------------------------------------------------------------------------------- /.github/workflows/vendored-release.yml: -------------------------------------------------------------------------------- 1 | name: Vendored Source tarball 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write # to upload assets to releases 13 | attestations: write # to upload assets attestation for build provenance 14 | id-token: write # grant additional permission to attestation action to mint the OIDC token permission 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version-file: go.mod 23 | 24 | - name: Vendor dependencies 25 | run: | 26 | go get 27 | go mod vendor 28 | tar czvf vib-vendored-deps.tar.gz * 29 | 30 | - name: Upload a Release Asset 31 | if: github.repository == 'Vanilla-OS/Vib' 32 | uses: softprops/action-gh-release@v2 33 | with: 34 | files: vib-vendored-deps.tar.gz 35 | 36 | - name: Attest generated files 37 | if: github.repository == 'Vanilla-OS/Vib' 38 | id: attest 39 | uses: actions/attest-build-provenance@v2 40 | with: 41 | subject-path: 'vib-vendored-deps.tar.gz' 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | core/plugins.go 2 | downloads/* 3 | example/downloads/* 4 | example/sources/* 5 | sources/* 6 | test/* 7 | build 8 | docs/website/dist/* 9 | docs/website/node_modules/* 10 | Containerfile 11 | go.work 12 | recipe.yml 13 | *~ 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .POSIX: 2 | 3 | PREFIX=/usr/ 4 | DESTDIR=/ 5 | BINARY_NAME=vib 6 | 7 | all: build # plugins 8 | 9 | build: FORCE 10 | mkdir -p build 11 | sed "s|%INSTALLPREFIX%|${PREFIX}|g" core/plugins.in > core/plugins.go 12 | go build -a -o build/${BINARY_NAME} 13 | 14 | build-plugins: FORCE 15 | mkdir -p build/plugins 16 | $(MAKE) -C plugins/ 17 | $(MAKE) -C finalize-plugins/ 18 | 19 | install: build 20 | install -Dm755 -t ${DESTDIR}/${PREFIX}/bin/ ./build/${BINARY_NAME} 21 | 22 | install-plugins: build-plugins 23 | install -Dm644 -t ${DESTDIR}/${PREFIX}/share/vib/plugins/ ./build/plugins/*.so 24 | 25 | clean: 26 | rm -r build 27 | rm core/plugins.go 28 | 29 | FORCE: 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |

Vib (Vanilla Image Builder) is a tool that streamlines the creation of container images. It achieves this by enabling users to define a recipe consisting of a sequence of modules, each specifying a particular action required to build the image. These actions may include installing dependencies or compiling source code. 5 |

6 |
7 |
8 | 9 | ## Links 10 | 11 | - [Website](https://vib.vanillaos.org/) 12 | - [Documentation](https://docs.vanillaos.org/collections/vib) 13 | - [Examples](https://vib.vanillaos.org/examples) 14 | 15 | ## Usage 16 | 17 | To build an image using a recipe, you can use the `vib` command: 18 | 19 | ```sh 20 | vib build recipe.yml 21 | ``` 22 | 23 | this will parse the recipe.yml to a Containerfile, which can be used to build 24 | the image with any container image builder, such as `docker` or `podman`. 25 | -------------------------------------------------------------------------------- /api/arch.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | func TestArch(onlyArches []string, targetArch string) bool { 4 | if len(onlyArches) == 0 { 5 | return true 6 | } 7 | for _, arch := range onlyArches { 8 | if arch == targetArch { 9 | return true 10 | } 11 | } 12 | return false 13 | } 14 | -------------------------------------------------------------------------------- /api/download.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "crypto/sha256" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | // Generate the destination path for the source based on its type and module name 15 | func GetSourcePath(source Source, moduleName string) string { 16 | if len(strings.TrimSpace(source.Path)) > 0 { 17 | return filepath.Join(moduleName, source.Path) 18 | } 19 | switch source.Type { 20 | case "git": 21 | repoName := strings.Split(source.URL, "/") 22 | return filepath.Join(moduleName, strings.ReplaceAll(repoName[len(repoName)-1], ".git", "")) 23 | case "tar": 24 | url := strings.Split(source.URL, "/") 25 | tarFile := strings.Split(url[len(url)-1], "?")[0] 26 | tarParts := strings.Split(tarFile, ".") 27 | if strings.TrimSpace(tarParts[len(tarParts)-2]) != "tar" { 28 | return filepath.Join(moduleName, strings.Join(tarParts[:len(tarParts)-1], ".")) 29 | } else { 30 | return filepath.Join(moduleName, strings.Join(tarParts[:len(tarParts)-2], ".")) 31 | } 32 | case "file": 33 | url := strings.Split(source.URL, "/") 34 | file := strings.Split(url[len(url)-1], "?")[0] 35 | fileParts := strings.Split(file, ".") 36 | return filepath.Join(moduleName, strings.Join(fileParts[:len(fileParts)-1], ".")) 37 | case "local": 38 | toplevelDir := strings.Split(source.URL, "/") 39 | return filepath.Join(moduleName, toplevelDir[len(toplevelDir)-1]) 40 | } 41 | 42 | return "" 43 | } 44 | 45 | // Download the source based on its type and validate its checksum 46 | func DownloadSource(recipe *Recipe, source Source, moduleName string) error { 47 | fmt.Printf("Downloading source: %s\n", source.URL) 48 | 49 | switch source.Type { 50 | case "git": 51 | return DownloadGitSource(recipe.DownloadsPath, source, moduleName) 52 | case "tar": 53 | err := DownloadTarSource(recipe.DownloadsPath, source, moduleName) 54 | if err != nil { 55 | return err 56 | } 57 | return checksumValidation(source, filepath.Join(recipe.DownloadsPath, GetSourcePath(source, moduleName), moduleName+".tar")) 58 | case "file": 59 | err := DownloadFileSource(recipe.DownloadsPath, source, moduleName) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | extension := filepath.Ext(source.URL) 65 | filename := fmt.Sprintf("%s%s", moduleName, extension) 66 | destinationPath := filepath.Join(recipe.DownloadsPath, GetSourcePath(source, moduleName), filename) 67 | 68 | return checksumValidation(source, destinationPath) 69 | case "local": 70 | return DownloadLocalSource(recipe.SourcesPath, source, moduleName) 71 | default: 72 | return fmt.Errorf("unsupported source type %s", source.Type) 73 | } 74 | } 75 | 76 | // Clone a specific tag from a Git repository to the destination directory 77 | func gitCloneTag(url, tag, dest string) error { 78 | cmd := exec.Command( 79 | "git", 80 | "clone", url, 81 | "--depth", "1", 82 | "--branch", tag, 83 | dest, 84 | ) 85 | return cmd.Run() 86 | } 87 | 88 | // Retrieve the latest Git repository commit hash for a given branch from the destination directory 89 | func gitGetLatestCommit(branch, dest string) (string, error) { 90 | cmd := exec.Command("git", "--no-pager", "log", "-n", "1", "--pretty=format:\"%H\"", branch) 91 | cmd.Dir = dest 92 | latest_tag, err := cmd.Output() 93 | if err != nil { 94 | return "", err 95 | } 96 | 97 | return strings.Trim(string(latest_tag), "\""), nil 98 | } 99 | 100 | // Check out a specific Git repository branch or commit in the destination directory 101 | func gitCheckout(value, dest string) error { 102 | cmd := exec.Command("git", "checkout", value) 103 | cmd.Stdout = os.Stdout 104 | cmd.Stderr = os.Stderr 105 | cmd.Dir = dest 106 | return cmd.Run() 107 | } 108 | 109 | // Download a Git source repository based on the specified tag, branch, or commit 110 | func DownloadGitSource(downloadPath string, source Source, moduleName string) error { 111 | fmt.Printf("Downloading git source: %s\n", source.URL) 112 | 113 | if source.URL == "" { 114 | return fmt.Errorf("missing git remote URL") 115 | } 116 | if source.Commit == "" && source.Tag == "" && source.Branch == "" { 117 | return fmt.Errorf("missing source commit, tag or branch") 118 | } 119 | 120 | dest := filepath.Join(downloadPath, GetSourcePath(source, moduleName)) 121 | os.MkdirAll(dest, 0o777) 122 | 123 | if source.Tag != "" { 124 | fmt.Printf("Using tag %s\n", source.Tag) 125 | return gitCloneTag(source.URL, source.Tag, dest) 126 | } 127 | 128 | fmt.Printf("Cloning repository: %s\n", source.URL) 129 | cmd := exec.Command("git", "clone", source.URL, dest) 130 | err := cmd.Run() 131 | if err != nil { 132 | return err 133 | } 134 | 135 | if source.Commit != "" { 136 | fmt.Printf("Checking out branch: %s\n", source.Branch) 137 | err := gitCheckout(source.Branch, dest) 138 | if err != nil { 139 | return err 140 | } 141 | } 142 | 143 | // Default to latest commit 144 | if len(strings.TrimSpace(source.Commit)) == 0 || strings.EqualFold(source.Commit, "latest") { 145 | source.Commit, err = gitGetLatestCommit(source.Branch, dest) 146 | if err != nil { 147 | return fmt.Errorf("could not get latest commit: %s", err.Error()) 148 | } 149 | } 150 | fmt.Printf("Resetting to commit: %s\n", source.Commit) 151 | return gitCheckout(source.Commit, dest) 152 | } 153 | 154 | // Download a tarball from the specified URL and save it to the destination path 155 | func DownloadTarSource(downloadPath string, source Source, moduleName string) error { 156 | fmt.Printf("Source is tar: %s\n", source.URL) 157 | // Create the destination path 158 | dest := filepath.Join(downloadPath, GetSourcePath(source, moduleName)) 159 | os.MkdirAll(dest, 0o777) 160 | // Download the resource 161 | res, err := http.Get(source.URL) 162 | if err != nil { 163 | return err 164 | } 165 | 166 | defer res.Body.Close() 167 | // Create the destination tar file 168 | file, err := os.Create(filepath.Join(dest, moduleName+".tar")) 169 | if err != nil { 170 | return err 171 | } 172 | // Close the file when the function ends 173 | defer file.Close() 174 | // Copy the response body to the destination file 175 | _, err = io.Copy(file, res.Body) 176 | if err != nil { 177 | return err 178 | } 179 | 180 | return nil 181 | } 182 | 183 | // Copies a local source for use during the build, skips the Download directory and copies directly into the source path 184 | func DownloadLocalSource(sourcesPath string, source Source, moduleName string) error { 185 | fmt.Printf("Source is local: %s\n", source.URL) 186 | dest := filepath.Join(sourcesPath, GetSourcePath(source, moduleName)) 187 | os.MkdirAll(dest, 0o777) 188 | fileInfo, err := os.Stat(source.URL) 189 | if err != nil { 190 | return err 191 | } 192 | if fileInfo.IsDir() { 193 | fmt.Println("ROOTDIR:: ", source.URL) 194 | root := os.DirFS(source.URL) 195 | return os.CopyFS(dest, root) 196 | } else { 197 | fileName := strings.Split(source.URL, "/") 198 | out, err := os.Create(filepath.Join(dest, fileName[len(fileName)-1])) 199 | if err != nil { 200 | return err 201 | } 202 | defer out.Close() 203 | 204 | in, err := os.Open(source.URL) 205 | if err != nil { 206 | return err 207 | } 208 | defer in.Close() 209 | 210 | _, err = io.Copy(out, in) 211 | if err != nil { 212 | return err 213 | } 214 | return nil 215 | } 216 | 217 | } 218 | 219 | // Move downloaded sources from the download path to the sources path 220 | func MoveSources(downloadPath string, sourcesPath string, sources []Source, moduleName string) error { 221 | fmt.Println("Moving sources for " + moduleName) 222 | 223 | err := os.MkdirAll(filepath.Join(sourcesPath, moduleName), 0777) 224 | if err != nil { 225 | return err 226 | } 227 | for _, source := range sources { 228 | err = MoveSource(downloadPath, sourcesPath, source, moduleName) 229 | if err != nil { 230 | return err 231 | } 232 | } 233 | 234 | return nil 235 | } 236 | 237 | // Move or extract a source from the download path to the sources path depending on its type 238 | // tarballs: extract 239 | // git repositories: move 240 | func MoveSource(downloadPath string, sourcesPath string, source Source, moduleName string) error { 241 | fmt.Printf("Moving source: %s\n", moduleName) 242 | 243 | err := os.MkdirAll(filepath.Join(sourcesPath, moduleName), 0777) 244 | if err != nil { 245 | return err 246 | } 247 | 248 | switch source.Type { 249 | case "git", "file": 250 | dest := GetSourcePath(source, moduleName) 251 | return os.Rename( 252 | filepath.Join(downloadPath, dest), 253 | filepath.Join(sourcesPath, dest), 254 | ) 255 | case "tar": 256 | os.MkdirAll(filepath.Join(sourcesPath, GetSourcePath(source, moduleName)), 0o777) 257 | cmd := exec.Command( 258 | "tar", 259 | "-xf", filepath.Join(downloadPath, GetSourcePath(source, moduleName), moduleName+".tar"), 260 | "-C", filepath.Join(sourcesPath, GetSourcePath(source, moduleName)), 261 | ) 262 | err := cmd.Run() 263 | if err != nil { 264 | return err 265 | } 266 | 267 | return os.Remove(filepath.Join(downloadPath, GetSourcePath(source, moduleName), moduleName+".tar")) 268 | case "local": 269 | return nil 270 | default: 271 | return fmt.Errorf("unsupported source type %s", source.Type) 272 | } 273 | } 274 | 275 | // Validate the checksum of the downloaded file 276 | func checksumValidation(source Source, path string) error { 277 | // No checksum provided 278 | if len(strings.TrimSpace(source.Checksum)) == 0 { 279 | return nil 280 | } 281 | 282 | // Open the file 283 | file, err := os.Open(path) 284 | if err != nil { 285 | return fmt.Errorf("could not open file: %v", err) 286 | } 287 | 288 | // Close the file when the function ends 289 | defer file.Close() 290 | 291 | // Calculate the checksum 292 | checksum := sha256.New() 293 | _, err = io.Copy(checksum, file) 294 | if err != nil { 295 | return fmt.Errorf("could not calculate checksum: %v", err) 296 | } 297 | 298 | // Validate the checksum based on source type 299 | calculatedChecksum := fmt.Sprintf("%x", checksum.Sum(nil)) 300 | if (source.Type == "tar" || source.Type == "file") && calculatedChecksum != source.Checksum { 301 | return fmt.Errorf("%s source module checksum doesn't match: expected %s, got %s", source.Type, source.Checksum, calculatedChecksum) 302 | } 303 | 304 | return nil 305 | } 306 | 307 | // Download a file source from a URL and save it to the specified download path. 308 | // Create necessary directories and handle file naming based on the URL extension. 309 | func DownloadFileSource(downloadPath string, source Source, moduleName string) error { 310 | fmt.Printf("Source is file: %s\n", source.URL) 311 | 312 | destDir := filepath.Join(downloadPath, GetSourcePath(source, moduleName)) 313 | os.MkdirAll(destDir, 0o777) 314 | // Download the resource 315 | res, err := http.Get(source.URL) 316 | if err != nil { 317 | return err 318 | } 319 | 320 | defer res.Body.Close() 321 | // Create the destination file 322 | extension := filepath.Ext(source.URL) 323 | filename := fmt.Sprintf("%s%s", moduleName, extension) 324 | dest := filepath.Join(destDir, filename) 325 | 326 | file, err := os.Create(dest) 327 | if err != nil { 328 | return err 329 | } 330 | // Close the file when the function ends 331 | defer file.Close() 332 | // Copy the response body to the destination file 333 | _, err = io.Copy(file, res.Body) 334 | if err != nil { 335 | return err 336 | } 337 | 338 | return nil 339 | } 340 | -------------------------------------------------------------------------------- /api/finalize-scopes.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // / Get the final tagged Image name 4 | var IMAGENAME int32 = 1 5 | 6 | // / Get the final tagged Image ID 7 | var IMAGEID int32 = 2 8 | 9 | // / Get the build recipe 10 | var RECIPE int32 = 4 11 | 12 | // Get the used build runtime 13 | var RUNTIME int32 = 8 14 | 15 | // / Get a read-only filesystem of the Image 16 | var FS int32 = 16 17 | 18 | // Information about the image, recipe, runtime, and file system mountpoint 19 | type ScopeData struct { 20 | ImageName string 21 | ImageID string 22 | Recipe Recipe 23 | Runtime string 24 | FS string 25 | } 26 | -------------------------------------------------------------------------------- /api/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vanilla-os/vib/api 2 | 3 | go 1.23.0 4 | -------------------------------------------------------------------------------- /api/structs.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // Configuration for a source 4 | type Source struct { 5 | URL string `json:"url"` 6 | Checksum string `json:"checksum"` 7 | Type string `json:"type"` 8 | Commit string `json:"commit"` 9 | Tag string `json:"tag"` 10 | Branch string `json:"branch"` 11 | Packages []string `json:"packages"` 12 | Path string `json:"path"` 13 | OnlyArches []string `json:"only-arches" mapstructure:"only-arches"` 14 | } 15 | 16 | // Configuration for a recipe 17 | type Recipe struct { 18 | Name string 19 | Id string 20 | Vibversion string 21 | Stages []Stage 22 | Path string 23 | ParentPath string 24 | DownloadsPath string 25 | SourcesPath string 26 | IncludesPath string 27 | PluginPath string 28 | Containerfile string 29 | Finalize []interface{} 30 | } 31 | 32 | // Configuration for a stage in the recipe 33 | type Stage struct { 34 | Id string `json:"id"` 35 | Base string `json:"base"` 36 | Copy []Copy `json:"copy"` 37 | Addincludes bool `json:"addincludes"` 38 | Labels map[string]string `json:"labels"` 39 | Env map[string]string `json:"env"` 40 | Adds []Add `json:"adds"` 41 | Args map[string]string `json:"args"` 42 | Runs Run `json:"runs"` 43 | Expose map[string]string `json:"expose"` 44 | Cmd Cmd `json:"cmd"` 45 | Modules []interface{} `json:"modules"` 46 | Entrypoint Entrypoint 47 | } 48 | 49 | type PluginType int 50 | 51 | const ( 52 | BuildPlugin PluginType = iota 53 | FinalizePlugin 54 | ) 55 | 56 | // Information about a plugin 57 | type PluginInfo struct { 58 | Name string 59 | Type PluginType 60 | UseContainerCmds bool 61 | } 62 | 63 | // Configuration for copying files or directories in a stage 64 | type Copy struct { 65 | From string 66 | SrcDst map[string]string 67 | Workdir string 68 | } 69 | 70 | // Configuration for adding files or directories in a stage 71 | type Add struct { 72 | SrcDst map[string]string 73 | Workdir string 74 | } 75 | 76 | // Configuration for the entrypoint of a container 77 | type Entrypoint struct { 78 | Exec []string 79 | Workdir string 80 | } 81 | 82 | // Configuration for a command to run in the container 83 | type Cmd struct { 84 | Exec []string 85 | Workdir string 86 | } 87 | 88 | // Configuration for commands to run in the container 89 | type Run struct { 90 | Commands []string 91 | Workdir string 92 | } 93 | -------------------------------------------------------------------------------- /cmd/build.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "runtime" 9 | "strings" 10 | 11 | "github.com/spf13/cobra" 12 | "github.com/vanilla-os/vib/core" 13 | ) 14 | 15 | // Create a new build command for the Cobra CLI 16 | // 17 | // Returns: new Cobra command for building a recipe 18 | func NewBuildCommand() *cobra.Command { 19 | cmd := &cobra.Command{ 20 | Use: "build", 21 | Short: "Build the given recipe", 22 | Long: "Build the given Vib recipe into a Containerfile", 23 | Example: ` Using the recipe.yml/yaml or vib.yml/yaml file in the current directory: 24 | vib build 25 | 26 | To specify a recipe file, use: 27 | vib build /path/to/recipe.yml`, 28 | RunE: buildCommand, 29 | } 30 | cmd.Flags().StringP("arch", "a", runtime.GOARCH, "target architecture") 31 | cmd.Flags().SetInterspersed(false) 32 | 33 | return cmd 34 | } 35 | 36 | // Handle the build command for the Cobra CLI 37 | func buildCommand(cmd *cobra.Command, args []string) error { 38 | commonNames := []string{ 39 | "recipe.yml", 40 | "recipe.yaml", 41 | "vib.yml", 42 | "vib.yaml", 43 | } 44 | var recipePath string 45 | var arch string 46 | 47 | arch, _ = cmd.Flags().GetString("arch") 48 | 49 | if len(args) == 0 { 50 | for _, name := range commonNames { 51 | if _, err := os.Stat(name); err == nil { 52 | recipePath = name 53 | break 54 | } 55 | } 56 | } else { 57 | recipePath = args[0] 58 | 59 | /* 60 | Check whether the provided file has either yml or yaml extension, 61 | if not, then return an error 62 | 63 | Operations on recipePath: 64 | 1. Get the recipePath extension, then 65 | 2. Trim the left dot(.) and 66 | 3. Convert the extension to lower case. 67 | 68 | Covers the following: 69 | 1. filename.txt - Invalid extension 70 | 2. filename. - No extension 71 | 3. filename - No extension 72 | 4. filename.YAML or filename.YML - uppercase extension 73 | */ 74 | extension := strings.ToLower(strings.TrimLeft(filepath.Ext(recipePath), ".")) 75 | if len(extension) == 0 || (extension != "yml" && extension != "yaml") { 76 | return fmt.Errorf("%s is an invalid recipe file", recipePath) 77 | } 78 | 79 | // Check whether the provided file exists, if not, then return an error 80 | if _, err := os.Stat(recipePath); errors.Is(err, os.ErrNotExist) { 81 | return fmt.Errorf("%s does not exist", recipePath) 82 | } 83 | } 84 | 85 | if recipePath == "" { 86 | return fmt.Errorf("missing recipe path") 87 | } 88 | 89 | _, err := core.BuildRecipe(recipePath, arch) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | return nil 95 | } 96 | -------------------------------------------------------------------------------- /cmd/compile.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "runtime" 8 | 9 | "github.com/spf13/cobra" 10 | "github.com/vanilla-os/vib/core" 11 | ) 12 | 13 | // Create and return a new compile command for the Cobra CLI 14 | func NewCompileCommand() *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: "compile", 17 | Short: "Compile the given recipe", 18 | Long: "Compile the given Vib recipe into a working container image, using the specified runtime (docker/podman)", 19 | Example: ` vib compile // using the recipe in the current directory and the system's default runtime 20 | vib compile --runtime podman // using the recipe in the current directory and Podman as the runtime 21 | vib compile /path/to/recipe.yml --runtime podman // using the recipe at the specified path and Podman as the runtime 22 | Both docker and podman are supported as runtimes. If none is specified, the detected runtime will be used, giving priority to Docker.`, 23 | RunE: compileCommand, 24 | } 25 | cmd.Flags().StringP("runtime", "r", "", "The runtime to use (docker/podman)") 26 | cmd.Flags().SetInterspersed(false) 27 | 28 | return cmd 29 | } 30 | 31 | // Execute the compile command: compile the given recipe into a container image 32 | func compileCommand(cmd *cobra.Command, args []string) error { 33 | commonNames := []string{ 34 | "recipe.yml", 35 | "recipe.yaml", 36 | "vib.yml", 37 | "vib.yaml", 38 | } 39 | var recipePath string 40 | var arch string 41 | var containerRuntime string 42 | 43 | arch = runtime.GOARCH 44 | containerRuntime, _ = cmd.Flags().GetString("runtime") 45 | 46 | if len(args) == 0 { 47 | for _, name := range commonNames { 48 | if _, err := os.Stat(name); err == nil { 49 | recipePath = name 50 | break 51 | } 52 | } 53 | } else { 54 | recipePath = args[0] 55 | } 56 | 57 | if recipePath == "" { 58 | return fmt.Errorf("missing recipe path") 59 | } 60 | 61 | detectedRuntime := detectRuntime() 62 | if containerRuntime == "" && detectedRuntime == "" { 63 | return fmt.Errorf("missing runtime, and no one was detected") 64 | } else if containerRuntime == "" { 65 | containerRuntime = detectedRuntime 66 | } 67 | 68 | err := core.CompileRecipe(recipePath, arch, containerRuntime, IsRoot, OrigGID, OrigUID) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | return nil 74 | } 75 | 76 | // Detect the container runtime by checking the system path 77 | // 78 | // Returns: runtime name or an empty string if no runtime is found 79 | func detectRuntime() string { 80 | path, _ := exec.LookPath("docker") 81 | if path != "" { 82 | return "docker" 83 | } 84 | 85 | path, _ = exec.LookPath("podman") 86 | if path != "" { 87 | return "podman" 88 | } 89 | 90 | return "" 91 | } 92 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "strconv" 8 | "syscall" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var Version = "0.0.0" 14 | var IsRoot = false 15 | var OrigUID = 1000 16 | var OrigGID = 1000 17 | var OrigUser = "user" 18 | 19 | var rootCmd = &cobra.Command{ 20 | Use: "vib", 21 | Short: "Vib is a tool to build container images from recipes using modules", 22 | Long: "Vib is a tool to build container images from YAML recipes using modules to define the steps to build the image.", 23 | SilenceUsage: true, 24 | Version: Version, 25 | } 26 | 27 | // Initialize the root command with build, test, and compile commands 28 | func init() { 29 | rootCmd.AddCommand(NewBuildCommand()) 30 | rootCmd.AddCommand(NewTestCommand()) 31 | rootCmd.AddCommand(NewCompileCommand()) 32 | } 33 | 34 | // Execute the root command, handling root user environment setup and privilege dropping 35 | func Execute() error { 36 | if os.Getuid() == 0 { 37 | IsRoot = true 38 | gid, err := strconv.Atoi(os.Getenv("SUDO_GID")) 39 | if err != nil { 40 | return fmt.Errorf("failed to get user gid through SUDO_GID: %s", err.Error()) 41 | } 42 | OrigGID = gid // go moment?? 43 | 44 | uid, err := strconv.Atoi(os.Getenv("SUDO_UID")) 45 | if err != nil { 46 | return fmt.Errorf("failed to get user uid through SUDO_UID: %s", err.Error()) 47 | } 48 | OrigUID = uid 49 | 50 | user := os.Getenv("SUDO_USER") 51 | os.Setenv("HOME", filepath.Join("/home", user)) 52 | 53 | err = syscall.Setegid(OrigGID) 54 | if err != nil { 55 | fmt.Println("WARN: Failed to drop GID root privileges ", OrigGID) 56 | 57 | } 58 | err = syscall.Seteuid(OrigUID) 59 | if err != nil { 60 | fmt.Println("WARN: Failed to drop UID root privileges ", OrigUID) 61 | } 62 | } 63 | return rootCmd.Execute() 64 | } 65 | -------------------------------------------------------------------------------- /cmd/test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/vanilla-os/vib/core" 8 | ) 9 | 10 | // Create and return a new test command for the Cobra CLI 11 | func NewTestCommand() *cobra.Command { 12 | cmd := &cobra.Command{ 13 | Use: "test", 14 | Short: "Test the given recipe", 15 | Long: "Test the given Vib recipe to check if it's valid", 16 | RunE: testCommand, 17 | } 18 | cmd.Flags().SetInterspersed(false) 19 | 20 | return cmd 21 | } 22 | 23 | // Validate the provided recipe by testing it 24 | func testCommand(cmd *cobra.Command, args []string) error { 25 | if len(args) == 0 { 26 | return fmt.Errorf("no recipe path specified") 27 | } 28 | 29 | recipePath := args[0] 30 | _, err := core.TestRecipe(recipePath) 31 | if err != nil { 32 | return err 33 | } 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /core/build.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/mitchellh/mapstructure" 11 | "github.com/vanilla-os/vib/api" 12 | ) 13 | 14 | // Add a WORKDIR instruction to the containerfile 15 | func ChangeWorkingDirectory(workdir string, containerfile *os.File) error { 16 | if workdir != "" { 17 | _, err := containerfile.WriteString( 18 | fmt.Sprintf("WORKDIR %s\n", workdir), 19 | ) 20 | if err != nil { 21 | return err 22 | } 23 | } 24 | return nil 25 | } 26 | 27 | // Add a WORKDIR instruction to reset to the root directory 28 | func RestoreWorkingDirectory(workdir string, containerfile *os.File) error { 29 | if workdir != "" { 30 | _, err := containerfile.WriteString( 31 | fmt.Sprintf("WORKDIR %s\n", "/"), 32 | ) 33 | if err != nil { 34 | return err 35 | } 36 | } 37 | 38 | return nil 39 | } 40 | 41 | // Load and build a Containerfile from the specified recipe 42 | func BuildRecipe(recipePath string, arch string) (api.Recipe, error) { 43 | // load the recipe 44 | recipe, err := LoadRecipe(recipePath) 45 | if err != nil { 46 | return api.Recipe{}, err 47 | } 48 | 49 | fmt.Printf("Building recipe %s\n", recipe.Name) 50 | 51 | // build the Containerfile 52 | err = BuildContainerfile(recipe, arch) 53 | if err != nil { 54 | return api.Recipe{}, err 55 | } 56 | 57 | modules := 0 58 | for _, stage := range recipe.Stages { 59 | modules += len(stage.Modules) 60 | } 61 | 62 | fmt.Printf("Recipe %s built successfully\n", recipe.Name) 63 | fmt.Printf("Processed %d stages\n", len(recipe.Stages)) 64 | fmt.Printf("Processed %d modules\n", modules) 65 | 66 | return *recipe, nil 67 | } 68 | 69 | // Generate a Containerfile from the recipe 70 | func BuildContainerfile(recipe *api.Recipe, arch string) error { 71 | containerfile, err := os.Create(recipe.Containerfile) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | defer containerfile.Close() 77 | 78 | for _, stage := range recipe.Stages { 79 | // build the modules* 80 | // * actually just build the commands that will be used 81 | // in the Containerfile to build the modules 82 | cmds, err := BuildModules(recipe, stage.Modules, arch) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | // FROM 88 | if stage.Id != "" { 89 | _, err = containerfile.WriteString( 90 | fmt.Sprintf("# Stage: %s\n", stage.Id), 91 | ) 92 | if err != nil { 93 | return err 94 | } 95 | _, err = containerfile.WriteString( 96 | fmt.Sprintf("FROM %s AS %s\n", stage.Base, stage.Id), 97 | ) 98 | if err != nil { 99 | return err 100 | } 101 | } else { 102 | _, err = containerfile.WriteString( 103 | fmt.Sprintf("FROM %s\n", stage.Base), 104 | ) 105 | if err != nil { 106 | return err 107 | } 108 | } 109 | 110 | // COPY 111 | if len(stage.Copy) > 0 { 112 | for _, copy := range stage.Copy { 113 | if len(copy.SrcDst) > 0 { 114 | err = ChangeWorkingDirectory(copy.Workdir, containerfile) 115 | if err != nil { 116 | return err 117 | } 118 | 119 | for src, dst := range copy.SrcDst { 120 | if copy.From != "" { 121 | _, err = containerfile.WriteString( 122 | fmt.Sprintf("COPY --from=%s %s %s\n", copy.From, src, dst), 123 | ) 124 | if err != nil { 125 | return err 126 | } 127 | } else { 128 | _, err = containerfile.WriteString( 129 | fmt.Sprintf("COPY %s %s\n", src, dst), 130 | ) 131 | if err != nil { 132 | return err 133 | } 134 | } 135 | } 136 | 137 | err = RestoreWorkingDirectory(copy.Workdir, containerfile) 138 | if err != nil { 139 | return err 140 | } 141 | } 142 | } 143 | } 144 | 145 | // LABELS 146 | for key, value := range stage.Labels { 147 | _, err = containerfile.WriteString( 148 | fmt.Sprintf("LABEL %s='%s'\n", key, value), 149 | ) 150 | if err != nil { 151 | return err 152 | } 153 | } 154 | 155 | // ENV 156 | for key, value := range stage.Env { 157 | _, err = containerfile.WriteString( 158 | fmt.Sprintf("ENV %s=%s\n", key, value), 159 | ) 160 | if err != nil { 161 | return err 162 | } 163 | } 164 | 165 | // ARGS 166 | for key, value := range stage.Args { 167 | _, err = containerfile.WriteString( 168 | fmt.Sprintf("ARG %s=%s\n", key, value), 169 | ) 170 | if err != nil { 171 | return err 172 | } 173 | } 174 | 175 | // RUN(S) 176 | if len(stage.Runs.Commands) > 0 { 177 | err = ChangeWorkingDirectory(stage.Runs.Workdir, containerfile) 178 | if err != nil { 179 | return err 180 | } 181 | 182 | for _, cmd := range stage.Runs.Commands { 183 | _, err = containerfile.WriteString( 184 | fmt.Sprintf("RUN %s\n", cmd), 185 | ) 186 | if err != nil { 187 | return err 188 | } 189 | } 190 | 191 | err = RestoreWorkingDirectory(stage.Runs.Workdir, containerfile) 192 | if err != nil { 193 | return err 194 | } 195 | } 196 | 197 | // EXPOSE 198 | for key, value := range stage.Expose { 199 | _, err = containerfile.WriteString( 200 | fmt.Sprintf("EXPOSE %s/%s\n", key, value), 201 | ) 202 | if err != nil { 203 | return err 204 | } 205 | } 206 | 207 | // ADDS 208 | if len(stage.Adds) > 0 { 209 | for _, add := range stage.Adds { 210 | if len(add.SrcDst) > 0 { 211 | err = ChangeWorkingDirectory(add.Workdir, containerfile) 212 | if err != nil { 213 | return err 214 | } 215 | 216 | for src, dst := range add.SrcDst { 217 | _, err = containerfile.WriteString( 218 | fmt.Sprintf("ADD %s %s\n", src, dst), 219 | ) 220 | if err != nil { 221 | return err 222 | } 223 | } 224 | } 225 | 226 | err = RestoreWorkingDirectory(add.Workdir, containerfile) 227 | if err != nil { 228 | return err 229 | } 230 | } 231 | } 232 | 233 | // INCLUDES.CONTAINER 234 | if stage.Addincludes { 235 | _, err = containerfile.WriteString(fmt.Sprintf("ADD %s /\n", recipe.IncludesPath)) 236 | if err != nil { 237 | return err 238 | } 239 | } 240 | 241 | for _, cmd := range cmds { 242 | err = ChangeWorkingDirectory(cmd.Workdir, containerfile) 243 | if err != nil { 244 | return err 245 | } 246 | 247 | _, err = containerfile.WriteString(strings.Join(cmd.Command, "\n")) 248 | if err != nil { 249 | return err 250 | } 251 | 252 | err = RestoreWorkingDirectory(cmd.Workdir, containerfile) 253 | if err != nil { 254 | return err 255 | } 256 | } 257 | 258 | // CMD 259 | err = ChangeWorkingDirectory(stage.Cmd.Workdir, containerfile) 260 | if err != nil { 261 | return err 262 | } 263 | 264 | if len(stage.Cmd.Exec) > 0 { 265 | _, err = containerfile.WriteString( 266 | fmt.Sprintf("CMD [\"%s\"]\n", strings.Join(stage.Cmd.Exec, "\",\"")), 267 | ) 268 | if err != nil { 269 | return err 270 | } 271 | 272 | err = RestoreWorkingDirectory(stage.Cmd.Workdir, containerfile) 273 | if err != nil { 274 | return err 275 | } 276 | } 277 | 278 | // ENTRYPOINT 279 | err = ChangeWorkingDirectory(stage.Entrypoint.Workdir, containerfile) 280 | if err != nil { 281 | return err 282 | } 283 | 284 | if len(stage.Entrypoint.Exec) > 0 { 285 | _, err = containerfile.WriteString( 286 | fmt.Sprintf("ENTRYPOINT [\"%s\"]\n", strings.Join(stage.Entrypoint.Exec, "\",\"")), 287 | ) 288 | if err != nil { 289 | return err 290 | } 291 | 292 | err = RestoreWorkingDirectory(stage.Entrypoint.Workdir, containerfile) 293 | if err != nil { 294 | return err 295 | } 296 | } 297 | } 298 | 299 | return nil 300 | } 301 | 302 | // Build commands for each module in the recipe 303 | func BuildModules(recipe *api.Recipe, modules []interface{}, arch string) ([]ModuleCommand, error) { 304 | cmds := []ModuleCommand{} 305 | for _, moduleInterface := range modules { 306 | var module Module 307 | err := mapstructure.Decode(moduleInterface, &module) 308 | if err != nil { 309 | return nil, err 310 | } 311 | 312 | cmd, err := BuildModule(recipe, moduleInterface, arch) 313 | if err != nil { 314 | return nil, err 315 | } 316 | 317 | cmds = append(cmds, ModuleCommand{ 318 | Name: module.Name, 319 | Command: append(cmd, ""), // add empty entry to ensure proper newline in Containerfile 320 | Workdir: module.Workdir, 321 | }) 322 | } 323 | 324 | return cmds, nil 325 | } 326 | 327 | func buildIncludesModule(moduleInterface interface{}, recipe *api.Recipe, arch string) (string, error) { 328 | var include IncludesModule 329 | err := mapstructure.Decode(moduleInterface, &include) 330 | if err != nil { 331 | return "", err 332 | } 333 | 334 | if len(include.Includes) == 0 { 335 | return "", errors.New("includes module must have at least one module to include") 336 | } 337 | 338 | var commands []string 339 | for _, include := range include.Includes { 340 | var modulePath string 341 | 342 | // in case of a remote include, we need to download the 343 | // recipe before including it 344 | if include[:4] == "http" { 345 | fmt.Printf("Downloading recipe from %s\n", include) 346 | modulePath, err = downloadRecipe(include) 347 | if err != nil { 348 | return "", err 349 | } 350 | } else if followsGhPattern(include) { 351 | // if the include follows the github pattern, we need to 352 | // download the recipe from the github repository 353 | fmt.Printf("Downloading recipe from %s\n", include) 354 | modulePath, err = downloadGhRecipe(include) 355 | if err != nil { 356 | return "", err 357 | } 358 | } else { 359 | modulePath = filepath.Join(recipe.ParentPath, include) 360 | } 361 | 362 | includeModule, err := GenModule(modulePath) 363 | if err != nil { 364 | return "", err 365 | } 366 | 367 | buildModule, err := BuildModule(recipe, includeModule, arch) 368 | if err != nil { 369 | return "", err 370 | } 371 | commands = append(commands, buildModule...) 372 | } 373 | return strings.Join(commands, "\n"), nil 374 | } 375 | 376 | // Build a command string for the given module in the recipe 377 | func BuildModule(recipe *api.Recipe, moduleInterface interface{}, arch string) ([]string, error) { 378 | var module Module 379 | err := mapstructure.Decode(moduleInterface, &module) 380 | if err != nil { 381 | return []string{""}, err 382 | } 383 | 384 | fmt.Printf("Building module [%s] of type [%s]\n", module.Name, module.Type) 385 | 386 | commands := []string{fmt.Sprintf("\n# Begin Module %s - %s", module.Name, module.Type)} 387 | 388 | if len(module.Modules) > 0 { 389 | for _, nestedModule := range module.Modules { 390 | buildModule, err := BuildModule(recipe, nestedModule, arch) 391 | if err != nil { 392 | return []string{""}, err 393 | } 394 | commands = append(commands, buildModule...) 395 | } 396 | } 397 | 398 | moduleBuilders := map[string]func(interface{}, *api.Recipe, string) (string, error){ 399 | "shell": BuildShellModule, 400 | "includes": buildIncludesModule, 401 | } 402 | 403 | if moduleBuilder, ok := moduleBuilders[module.Type]; ok { 404 | command, err := moduleBuilder(moduleInterface, recipe, arch) 405 | if err != nil { 406 | return []string{""}, err 407 | } 408 | commands = append(commands, command) 409 | } else { 410 | command, err := LoadBuildPlugin(module.Type, moduleInterface, recipe, arch) 411 | if err != nil { 412 | return []string{""}, err 413 | } 414 | commands = append(commands, command...) 415 | } 416 | 417 | _ = os.MkdirAll(fmt.Sprintf("%s/%s", recipe.SourcesPath, module.Name), 0755) 418 | 419 | dirInfo, err := os.Stat(filepath.Join(recipe.SourcesPath, module.Name)) 420 | if err != nil { 421 | return []string{""}, err 422 | } 423 | if dirInfo.Size() > 0 { 424 | commands = append([]string{fmt.Sprintf("ADD sources/%s /sources/%s", module.Name, module.Name)}, commands...) 425 | commands = append(commands, fmt.Sprintf("RUN rm -rf /sources/%s", module.Name)) 426 | } 427 | commands = append(commands, fmt.Sprintf("# End Module %s - %s\n", module.Name, module.Type)) 428 | 429 | fmt.Printf("Module [%s] built successfully\n", module.Name) 430 | return commands, nil 431 | } 432 | -------------------------------------------------------------------------------- /core/compile.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "syscall" 8 | 9 | "github.com/mitchellh/mapstructure" 10 | "github.com/vanilla-os/vib/api" 11 | ) 12 | 13 | // Compile and build the recipe using the specified runtime 14 | func CompileRecipe(recipePath string, arch string, runtime string, isRoot bool, origGid int, origUid int) error { 15 | recipe, err := BuildRecipe(recipePath, arch) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | syscall.Setegid(0) 21 | syscall.Seteuid(0) 22 | switch runtime { 23 | case "docker": 24 | err = compileDocker(recipe, origGid, origUid) 25 | if err != nil { 26 | return err 27 | } 28 | case "podman": 29 | err = compilePodman(recipe, origGid, origUid) 30 | if err != nil { 31 | return err 32 | } 33 | case "buildah": 34 | return fmt.Errorf("buildah not implemented yet") 35 | default: 36 | return fmt.Errorf("no runtime specified and the prometheus library is not implemented yet") 37 | } 38 | syscall.Setegid(origGid) 39 | syscall.Seteuid(origUid) 40 | 41 | for _, finalizeInterface := range recipe.Finalize { 42 | var module Finalize 43 | 44 | err := mapstructure.Decode(finalizeInterface, &module) 45 | if err != nil { 46 | return err 47 | } 48 | err = LoadFinalizePlugin(module.Type, finalizeInterface, &recipe, arch, runtime, isRoot, origGid, origUid) 49 | if err != nil { 50 | return err 51 | } 52 | } 53 | 54 | fmt.Printf("Image %s built successfully using %s\n", recipe.Id, runtime) 55 | 56 | return nil 57 | } 58 | 59 | // Build an OCI image using the specified recipe through Docker 60 | func compileDocker(recipe api.Recipe, gid int, uid int) error { 61 | docker, err := exec.LookPath("docker") 62 | if err != nil { 63 | return err 64 | } 65 | 66 | cmd := exec.Command( 67 | docker, "build", 68 | "-t", fmt.Sprintf("localhost/%s", recipe.Id), 69 | "-f", recipe.Containerfile, 70 | ".", 71 | ) 72 | cmd.Stdout = os.Stdout 73 | cmd.Stderr = os.Stderr 74 | cmd.Dir = recipe.ParentPath 75 | 76 | return cmd.Run() 77 | } 78 | 79 | // Build an OCI image using the specified recipe through Podman 80 | func compilePodman(recipe api.Recipe, gid int, uid int) error { 81 | podman, err := exec.LookPath("podman") 82 | if err != nil { 83 | return err 84 | } 85 | 86 | cmd := exec.Command( 87 | podman, "build", 88 | "-t", fmt.Sprintf("localhost/%s", recipe.Id), 89 | "-f", recipe.Containerfile, 90 | ".", 91 | ) 92 | cmd.Stdout = os.Stdout 93 | cmd.Stderr = os.Stderr 94 | cmd.Dir = recipe.ParentPath 95 | 96 | return cmd.Run() 97 | } 98 | -------------------------------------------------------------------------------- /core/finalize.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | cstorage "github.com/containers/storage" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | // Configuration for storage drivers 11 | type StorageConf struct { 12 | Driver string 13 | Runroot string 14 | Graphroot string 15 | } 16 | 17 | // Retrieve the container storage configuration based on the runtime 18 | func GetContainerStorage(runtime string) (cstorage.Store, error) { 19 | storageconfig := &StorageConf{} 20 | if runtime == "podman" { 21 | podmanPath, err := exec.LookPath("podman") 22 | out, err := exec.Command( 23 | podmanPath, "info", "-f json").Output() 24 | if err != nil { 25 | fmt.Println("Failed to get podman info") 26 | } else { 27 | driver := strings.Split(strings.Split(string(out), "\"graphDriverName\": \"")[1], "\",")[0] 28 | storageconfig.Driver = driver 29 | 30 | graphRoot := strings.Split(strings.Split(string(out), "\"graphRoot\": \"")[1], "\",")[0] 31 | storageconfig.Graphroot = graphRoot 32 | 33 | runRoot := strings.Split(strings.Split(string(out), "\"runRoot\": \"")[1], "\",")[0] 34 | storageconfig.Runroot = runRoot 35 | } 36 | 37 | } 38 | if storageconfig.Runroot == "" { 39 | storageconfig.Runroot = "/var/lib/vib/runroot" 40 | storageconfig.Graphroot = "/var/lib/vib/graphroot" 41 | storageconfig.Driver = "overlay" 42 | } 43 | store, err := cstorage.GetStore(cstorage.StoreOptions{ 44 | RunRoot: storageconfig.Runroot, 45 | GraphRoot: storageconfig.Graphroot, 46 | GraphDriverName: storageconfig.Driver, 47 | }) 48 | if err != nil { 49 | return store, err 50 | } 51 | 52 | return store, err 53 | } 54 | 55 | // Retrieve the image ID for a given image name from the storage 56 | func GetImageID(name string, store cstorage.Store) (string, error) { 57 | images, err := store.Images() 58 | if err != nil { 59 | return "", err 60 | } 61 | for _, img := range images { 62 | for _, imgname := range img.Names { 63 | if imgname == name { 64 | return img.ID, nil 65 | } 66 | } 67 | } 68 | return "", fmt.Errorf("image not found") 69 | } 70 | 71 | // Retrieve the top layer ID for a given image ID from the storage 72 | func GetTopLayerID(imageid string, store cstorage.Store) (string, error) { 73 | images, err := store.Images() 74 | if err != nil { 75 | return "", err 76 | } 77 | for _, img := range images { 78 | if img.ID == imageid { 79 | return img.TopLayer, nil 80 | } 81 | } 82 | return "", fmt.Errorf("no top layer for id %s found", imageid) 83 | } 84 | 85 | // Mount the image and return the mount directory 86 | func MountImage(imagename string, imageid string, runtime string) (string, error) { 87 | store, err := GetContainerStorage(runtime) 88 | if err != nil { 89 | return "", err 90 | } 91 | topLayerID, err := GetTopLayerID(imageid, store) 92 | if err != nil { 93 | return "", err 94 | } 95 | mountDir, err := store.Mount(topLayerID, "") 96 | if err != nil { 97 | return "", err 98 | } 99 | return mountDir, err 100 | } 101 | -------------------------------------------------------------------------------- /core/loader.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "math" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/vanilla-os/vib/api" 14 | "gopkg.in/yaml.v3" 15 | ) 16 | 17 | var Min_Recipe_Version = []uint8{1, 0, 0} 18 | 19 | // LoadRecipe loads a recipe from a file and returns a Recipe 20 | // Does not validate the recipe but it will catch some errors 21 | // a proper validation will be done in the future 22 | func LoadRecipe(path string) (*api.Recipe, error) { 23 | recipe := &api.Recipe{} 24 | 25 | // we use the absolute path to the recipe file as the 26 | // root path for the recipe and all its files 27 | recipePath, err := filepath.Abs(path) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | // here we open the recipe file and unmarshal it into 33 | // the Recipe struct, this is not a full validation 34 | // but it will catch some errors 35 | recipeFile, err := os.Open(recipePath) 36 | if err != nil { 37 | return nil, err 38 | } 39 | defer recipeFile.Close() 40 | 41 | recipeYAML, err := io.ReadAll(recipeFile) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | err = yaml.Unmarshal(recipeYAML, recipe) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | if len(strings.TrimSpace(recipe.Vibversion)) <= 0 { 52 | return nil, fmt.Errorf("version key not found in recipe file, assuming outdated recipe") 53 | } 54 | 55 | recipeVersionS := strings.Split(recipe.Vibversion, ".") 56 | if len(recipeVersionS) != 3 { 57 | return nil, fmt.Errorf("invalid version format, expected x.x.x, got %s", recipe.Vibversion) 58 | } 59 | 60 | recipeVersion := []uint8{0, 0, 0} 61 | for i := 0; i < len(recipeVersion); i++ { 62 | versionInt, err := strconv.ParseUint(recipeVersionS[i], 10, 8) 63 | if err != nil { 64 | return nil, err 65 | } 66 | if versionInt > math.MaxUint8 { 67 | recipeVersion[i] = math.MaxUint8 68 | } else { 69 | recipeVersion[i] = uint8(versionInt) 70 | } 71 | } 72 | 73 | if recipeVersion[0] < Min_Recipe_Version[0] || recipeVersion[1] < Min_Recipe_Version[1] || recipeVersion[2] < Min_Recipe_Version[2] { 74 | return nil, fmt.Errorf("outdated recipe, this version of vib supports recipes starting at version %s", strings.Join(strings.Fields(fmt.Sprint(Min_Recipe_Version)), ".")) 75 | } 76 | 77 | // the recipe path is stored in the recipe itself 78 | // for convenience 79 | recipe.Path = recipePath 80 | recipe.ParentPath = filepath.Dir(recipePath) 81 | 82 | // assuming the Containerfile location is relative 83 | recipe.Containerfile = filepath.Join(filepath.Dir(recipePath), "Containerfile") 84 | err = os.RemoveAll(recipe.Containerfile) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | // we create the sources directory which is the place where 90 | // all the sources will be stored and be available to all 91 | // the modules 92 | recipe.SourcesPath = filepath.Join(filepath.Dir(recipePath), "sources") 93 | err = os.RemoveAll(recipe.SourcesPath) 94 | if err != nil { 95 | return nil, err 96 | } 97 | err = os.MkdirAll(recipe.SourcesPath, 0755) 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | // the downloads directory is a transient directory, here all 103 | // the downloaded sources will be stored before being moved 104 | // to the sources directory. This is useful since some sources 105 | // types need to be extracted, this way we can extract them 106 | // directly to the sources directory after downloading them 107 | recipe.DownloadsPath = filepath.Join(filepath.Dir(recipePath), "downloads") 108 | err = os.RemoveAll(recipe.DownloadsPath) 109 | if err != nil { 110 | return nil, err 111 | } 112 | err = os.MkdirAll(recipe.DownloadsPath, 0755) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | // the plugins directory contains all plugins that vib can load 118 | // and use for unknown modules in the recipe 119 | recipe.PluginPath = filepath.Join(filepath.Dir(recipePath), "plugins") 120 | 121 | // the includes directory is the place where we store all the 122 | // files to be included in the container, this is useful for 123 | // example to include configuration files. Each file must follow 124 | // the File Hierarchy Standard (FHS) and be placed in the correct 125 | // directory. For example, if you want to include a file in 126 | // /etc/nginx/nginx.conf you must place it in includes/etc/nginx/nginx.conf 127 | // so it will be copied to the correct location in the container 128 | if len(strings.TrimSpace(recipe.IncludesPath)) == 0 { 129 | recipe.IncludesPath = filepath.Join("includes.container") 130 | } 131 | _, err = os.Stat(recipe.IncludesPath) 132 | if os.IsNotExist(err) { 133 | err := os.MkdirAll(recipe.IncludesPath, 0755) 134 | if err != nil { 135 | return nil, err 136 | } 137 | } 138 | 139 | for i, stage := range recipe.Stages { 140 | // here we check if the extra Adds path exists 141 | for _, add := range stage.Adds { 142 | for src := range add.SrcDst { 143 | fullPath := filepath.Join(filepath.Dir(recipePath), src) 144 | _, err = os.Stat(fullPath) 145 | if os.IsNotExist(err) { 146 | return nil, err 147 | } 148 | } 149 | } 150 | 151 | recipe.Stages[i] = stage 152 | } 153 | 154 | return recipe, nil 155 | } 156 | 157 | // downloadRecipe downloads a recipe from a remote URL and stores it to 158 | // a temporary file 159 | func downloadRecipe(url string) (path string, err error) { 160 | resp, err := http.Get(url) 161 | if err != nil { 162 | return "", err 163 | } 164 | defer resp.Body.Close() 165 | 166 | tmpFile, err := os.CreateTemp("", "vib-recipe-") 167 | if err != nil { 168 | return "", err 169 | } 170 | defer tmpFile.Close() 171 | 172 | _, err = io.Copy(tmpFile, resp.Body) 173 | if err != nil { 174 | return "", err 175 | } 176 | 177 | return tmpFile.Name(), nil 178 | } 179 | 180 | // followsGhPattern checks if a given path follows the pattern: 181 | // gh:org/repo:branch:path 182 | func followsGhPattern(s string) bool { 183 | parts := strings.Split(s, ":") 184 | if len(parts) != 4 { 185 | return false 186 | } 187 | 188 | if parts[0] != "gh" { 189 | return false 190 | } 191 | 192 | return true 193 | } 194 | 195 | // downloadGhRecipe downloads a recipe from a github repository and stores it to 196 | // a temporary file 197 | func downloadGhRecipe(gh string) (path string, err error) { 198 | parts := strings.Split(gh, ":") 199 | repo := parts[1] 200 | branch := parts[2] 201 | file := parts[3] 202 | 203 | url := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s", repo, branch, file) 204 | return downloadRecipe(url) 205 | } 206 | 207 | // GenModule generate a Module struct from a module path 208 | func GenModule(modulePath string) (map[string]interface{}, error) { 209 | var module map[string]interface{} 210 | 211 | moduleFile, err := os.Open(modulePath) 212 | if err != nil { 213 | return module, err 214 | } 215 | defer moduleFile.Close() 216 | 217 | moduleYAML, err := io.ReadAll(moduleFile) 218 | if err != nil { 219 | return module, err 220 | } 221 | 222 | err = yaml.Unmarshal(moduleYAML, &module) 223 | if err != nil { 224 | return module, err 225 | } 226 | 227 | return module, nil 228 | } 229 | 230 | // TestRecipe validates a recipe by loading it and checking for errors 231 | func TestRecipe(path string) (*api.Recipe, error) { 232 | recipe, err := LoadRecipe(path) 233 | if err != nil { 234 | fmt.Printf("Error validating recipe: %s\n", err) 235 | return nil, err 236 | } 237 | 238 | modules := 0 239 | for _, stage := range recipe.Stages { 240 | modules += len(stage.Modules) 241 | } 242 | 243 | fmt.Printf("Recipe %s validated successfully\n", recipe.Id) 244 | fmt.Printf("Found %d stages\n", len(recipe.Stages)) 245 | fmt.Printf("Found %d modules\n", modules) 246 | return recipe, nil 247 | } 248 | -------------------------------------------------------------------------------- /core/plugins.in: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "C" 5 | "encoding/json" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/ebitengine/purego" 10 | "github.com/vanilla-os/vib/api" 11 | ) 12 | import ( 13 | "errors" 14 | "encoding/base64" 15 | "os" 16 | "syscall" 17 | ) 18 | 19 | var openedBuildPlugins map[string]Plugin 20 | var openedFinalizePlugins map[string]Plugin 21 | 22 | func decodeBuildCmds(cmds string) ([]string, error) { 23 | splitCmds := strings.Split(cmds, ",") 24 | resCmds := []string{} 25 | for _, cmd := range splitCmds { 26 | decodedCmd, err := base64.StdEncoding.DecodeString(cmd) 27 | if err != nil { 28 | return []string{}, err 29 | } 30 | resCmds = append(resCmds, string(decodedCmd)) 31 | } 32 | return resCmds, nil 33 | } 34 | 35 | func LoadPlugin(name string, plugintype api.PluginType, recipe *api.Recipe) (uintptr, api.PluginInfo, error) { 36 | fmt.Println("Loading new plugin") 37 | 38 | projectPluginPath := fmt.Sprintf("%s/%s.so", recipe.PluginPath, name) 39 | 40 | installPrefixPath := fmt.Sprintf("%INSTALLPREFIX%/share/vib/plugins/%s.so", name) 41 | 42 | globalPluginPathsEnv, isXDDDefined := os.LookupEnv("XDG_DATA_DIRS") 43 | if !isXDDDefined || len(strings.TrimSpace(globalPluginPathsEnv)) == 0 { 44 | globalPluginPathsEnv = "/usr/local/share:/usr/share" 45 | } 46 | 47 | globalPluginPaths_split := strings.Split(globalPluginPathsEnv, ":") 48 | 49 | for index := range globalPluginPaths_split { 50 | // Resolve each directory to a *possible* plugin file path. 51 | globalPluginPaths_split[index] = fmt.Sprintf("%s/vib/plugins/%s.so", globalPluginPaths_split[index], name) 52 | } 53 | 54 | // Specify all the paths where the plugin file might be stored. 55 | // Give priority to the projects "plugins" directory, then 56 | // follow INSTALLPREFIX and $XDG_DATA_DIRS, respectively. 57 | var allPluginPaths = append([]string{projectPluginPath, installPrefixPath}, globalPluginPaths_split...) 58 | var lastIndex = len(allPluginPaths) - 1 59 | 60 | var loadedPlugin uintptr 61 | 62 | // LoadPlugin() is run once for every plugin, therefore 63 | // the size of the array is limited to the same number 64 | // of paths to search. 65 | var _errors = make([]error, len(allPluginPaths)) 66 | 67 | for index, path := range allPluginPaths { 68 | _, err := os.Stat(path) 69 | if err != nil { 70 | _errors = append(_errors, err) 71 | if index == lastIndex { 72 | // If the last available path doesn't exist, 73 | // panic with all the error messages. 74 | panic(errors.Join(_errors...)) 75 | } 76 | 77 | continue 78 | } 79 | 80 | loadedPlugin, err = purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_GLOBAL) 81 | if err != nil { 82 | _errors = append(_errors, err) 83 | if index == lastIndex { 84 | // If the last available plugin can't be loaded, 85 | // panic with all the error messages. 86 | panic(errors.Join(_errors...)) 87 | } 88 | 89 | continue 90 | } 91 | 92 | break 93 | } 94 | 95 | infoLoc, err := purego.Dlsym(loadedPlugin, "PlugInfo") 96 | if err != nil && !strings.Contains(err.Error(), "undefined symbol: PlugInfo") { 97 | fmt.Println(err) 98 | return loadedPlugin, api.PluginInfo{}, err 99 | } 100 | 101 | pluginInfo := &api.PluginInfo{} 102 | 103 | if infoLoc == 0 { 104 | fmt.Println("== WARN ==") 105 | fmt.Printf("Plugin %s does not contain function PlugInfo, assuming old BuildPlugin type\n", name) 106 | fmt.Printf("Please update the plugin or request the developer of the plugin to update it!\n") 107 | fmt.Println("== WARN ==") 108 | pluginInfo.Name = name 109 | pluginInfo.Type = api.BuildPlugin 110 | pluginInfo.UseContainerCmds = false 111 | } else { 112 | var pluginInfoFunc func() string 113 | purego.RegisterLibFunc(&pluginInfoFunc, loadedPlugin, "PlugInfo") 114 | json.Unmarshal([]byte(pluginInfoFunc()), &pluginInfo) 115 | } 116 | 117 | if pluginInfo.Type != plugintype { 118 | if plugintype == api.BuildPlugin { 119 | return loadedPlugin, *pluginInfo, fmt.Errorf("ERROR: Plugin %s is not of type BuildPlugin", name) 120 | } else if plugintype == api.FinalizePlugin { 121 | return loadedPlugin, *pluginInfo, fmt.Errorf("ERROR: Plugin %s is not of type FinalizePlugin", name) 122 | } 123 | } 124 | 125 | return loadedPlugin, *pluginInfo, nil 126 | } 127 | 128 | func LoadBuildPlugin(name string, module interface{}, recipe *api.Recipe, arch string) ([]string, error) { 129 | if openedBuildPlugins == nil { 130 | openedBuildPlugins = make(map[string]Plugin) 131 | } 132 | pluginOpened := false 133 | var buildModule Plugin 134 | buildModule, pluginOpened = openedBuildPlugins[name] 135 | if !pluginOpened { 136 | loadedPlugin, pluginInfo, err := LoadPlugin(name, api.BuildPlugin, recipe) 137 | if err != nil { 138 | return []string{""}, err 139 | } 140 | var buildFunction func(*C.char, *C.char, *C.char) string 141 | purego.RegisterLibFunc(&buildFunction, loadedPlugin, "BuildModule") 142 | buildModule.Name = name 143 | buildModule.BuildFunc = buildFunction 144 | buildModule.LoadedPlugin = loadedPlugin 145 | buildModule.PluginInfo = pluginInfo 146 | openedBuildPlugins[name] = buildModule 147 | } 148 | fmt.Printf("Using plugin: %s\n", buildModule.Name) 149 | moduleJson, err := json.Marshal(module) 150 | if err != nil { 151 | return []string{""}, err 152 | } 153 | recipeJson, err := json.Marshal(recipe) 154 | if err != nil { 155 | return []string{""}, err 156 | } 157 | 158 | res := buildModule.BuildFunc(C.CString(string(moduleJson)), C.CString(string(recipeJson)), C.CString(arch)) 159 | if strings.HasPrefix(res, "ERROR:") { 160 | return []string{""}, fmt.Errorf("%s", strings.Replace(res, "ERROR: ", "", 1)) 161 | } else if !buildModule.PluginInfo.UseContainerCmds { 162 | return []string{"RUN " + res}, nil 163 | } else { 164 | return decodeBuildCmds(res) 165 | } 166 | } 167 | 168 | func LoadFinalizePlugin(name string, module interface{}, recipe *api.Recipe, arch string, runtime string, isRoot bool, origGid int, origUid int) error { 169 | if openedFinalizePlugins == nil { 170 | openedFinalizePlugins = make(map[string]Plugin) 171 | } 172 | pluginOpened := false 173 | var finalizeModule Plugin 174 | finalizeModule, pluginOpened = openedFinalizePlugins[name] 175 | if !pluginOpened { 176 | loadedPlugin, pluginInfo, err := LoadPlugin(name, api.FinalizePlugin, recipe) 177 | if err != nil { 178 | return err 179 | } 180 | var finalizeFunction func(*C.char, *C.char, *C.char) string 181 | purego.RegisterLibFunc(&finalizeFunction, loadedPlugin, "FinalizeBuild") 182 | finalizeModule.Name = name 183 | finalizeModule.BuildFunc = finalizeFunction 184 | finalizeModule.LoadedPlugin = loadedPlugin 185 | finalizeModule.PluginInfo = pluginInfo 186 | openedFinalizePlugins[name] = finalizeModule 187 | } 188 | fmt.Printf("Using Finalize plugin: %s\n", finalizeModule.Name) 189 | 190 | syscall.Seteuid(0) 191 | syscall.Setegid(0) 192 | 193 | var getPluginScope func() int32 194 | purego.RegisterLibFunc(&getPluginScope, finalizeModule.LoadedPlugin, "PluginScope") 195 | scope := getPluginScope() 196 | containerStorage, err := GetContainerStorage(runtime) 197 | if err != nil { 198 | return err 199 | } 200 | imageName := fmt.Sprintf("localhost/%s:latest", recipe.Id) 201 | scopedata := &api.ScopeData{} 202 | if scope&api.IMAGENAME == api.IMAGENAME { 203 | scopedata.ImageName = imageName 204 | } 205 | if scope&api.IMAGEID == api.IMAGEID { 206 | imageID, err := GetImageID(imageName, containerStorage) 207 | if err != nil { 208 | return err 209 | } 210 | scopedata.ImageID = imageID 211 | } 212 | if scope&api.RECIPE == api.RECIPE { 213 | scopedata.Recipe = *recipe 214 | } 215 | if scope&api.RUNTIME == api.RUNTIME { 216 | scopedata.Runtime = runtime 217 | } 218 | if scope&api.FS == api.FS { 219 | if !isRoot { 220 | return fmt.Errorf("Plugin %s requires scope api.FS, which requires vib to run as root", finalizeModule.Name) 221 | } 222 | imageID, err := GetImageID(imageName, containerStorage) 223 | if err != nil { 224 | return err 225 | } 226 | mountpoint, err := MountImage(imageName, imageID, runtime) 227 | if err != nil { 228 | return err 229 | } 230 | scopedata.FS = mountpoint 231 | } 232 | moduleJson, err := json.Marshal(module) 233 | if err != nil { 234 | return err 235 | } 236 | scopeJson, err := json.Marshal(scopedata) 237 | if err != nil { 238 | return err 239 | } 240 | res := finalizeModule.BuildFunc(C.CString(string(moduleJson)), C.CString(string(scopeJson)), C.CString(arch)) 241 | syscall.Seteuid(origGid) 242 | syscall.Setegid(origUid) 243 | if strings.HasPrefix(res, "ERROR:") { 244 | return fmt.Errorf("%s", strings.Replace(res, "ERROR: ", "", 1)) 245 | } else { 246 | return nil 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /core/resolver_test.go: -------------------------------------------------------------------------------- 1 | package core_test 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/vanilla-os/vib/api" 9 | ) 10 | 11 | // Test the DownloadSource function to ensure it downloads and verifies the source file 12 | func TestDownloadSource(t *testing.T) { 13 | tmp := t.TempDir() 14 | 15 | source := api.Source{ 16 | Type: "tar", 17 | URL: "https://github.com/Vanilla-OS/Vib/archive/refs/tags/v0.3.1.tar.gz", 18 | Checksum: "d28ab888c7b30fd1cc01e0a581169ea52dfb5bfcefaca721497f82734b6a5a98", 19 | } 20 | err := api.DownloadSource(tmp, source, "test") 21 | if err != nil { 22 | t.Errorf("DownloadSource returned an error: %v", err) 23 | } 24 | 25 | // Check if the file was downloaded 26 | dest := filepath.Join(tmp, "test") 27 | if _, err := os.Stat(dest); os.IsNotExist(err) { 28 | t.Errorf("Downloaded file does not exist: %v", err) 29 | } 30 | defer os.Remove("/tmp/example") // clean up 31 | } 32 | 33 | // Test the DownloadTarSource function to ensure it downloads and verifies the tar file 34 | func TestDownloadTarSource(t *testing.T) { 35 | tmp := t.TempDir() 36 | 37 | source := api.Source{ 38 | Type: "tar", 39 | URL: "https://github.com/Vanilla-OS/Vib/archive/refs/tags/v0.3.1.tar.gz", 40 | Checksum: "d28ab888c7b30fd1cc01e0a581169ea52dfb5bfcefaca721497f82734b6a5a98", 41 | } 42 | err := api.DownloadTarSource(tmp, source, "test2") 43 | if err != nil { 44 | t.Errorf("DownloadTarSource returned an error: %v", err) 45 | } 46 | 47 | // Check if the file was downloaded 48 | dest := filepath.Join(tmp, "test2") 49 | if _, err := os.Stat(dest); os.IsNotExist(err) { 50 | t.Errorf("Downloaded file does not exist: %v", err) 51 | } 52 | defer os.Remove("/tmp/example") // clean up 53 | 54 | } 55 | -------------------------------------------------------------------------------- /core/shell.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | "github.com/mitchellh/mapstructure" 8 | "github.com/vanilla-os/vib/api" 9 | ) 10 | 11 | // Configuration for shell modules 12 | type ShellModule struct { 13 | Name string `json:"name"` 14 | Type string `json:"type"` 15 | Sources []api.Source 16 | Commands []string 17 | } 18 | 19 | // Build shell module commands and return them as a single string 20 | // 21 | // Returns: Concatenated shell commands or an error if any step fails 22 | func BuildShellModule(moduleInterface interface{}, recipe *api.Recipe, arch string) (string, error) { 23 | var module ShellModule 24 | err := mapstructure.Decode(moduleInterface, &module) 25 | if err != nil { 26 | return "", err 27 | } 28 | 29 | for _, source := range module.Sources { 30 | if api.TestArch(source.OnlyArches, arch) { 31 | if strings.TrimSpace(source.Type) != "" { 32 | err := api.DownloadSource(recipe, source, module.Name) 33 | if err != nil { 34 | return "", err 35 | } 36 | err = api.MoveSource(recipe.DownloadsPath, recipe.SourcesPath, source, module.Name) 37 | if err != nil { 38 | return "", err 39 | } 40 | } 41 | } 42 | } 43 | 44 | if len(module.Commands) == 0 { 45 | return "", errors.New("no commands specified") 46 | } 47 | 48 | cmd := "" 49 | for i, command := range module.Commands { 50 | cmd += command 51 | if i < len(module.Commands)-1 { 52 | cmd += " && " 53 | } 54 | } 55 | 56 | return "RUN " + cmd, nil 57 | } 58 | -------------------------------------------------------------------------------- /core/structs.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import "C" 4 | import "github.com/vanilla-os/vib/api" 5 | 6 | // Configuration for a module 7 | type Module struct { 8 | Name string `json:"name"` 9 | Workdir string 10 | Type string `json:"type"` 11 | Modules []map[string]interface{} 12 | Content []byte // The entire module unparsed as a []byte, used by plugins 13 | } 14 | 15 | // Configuration for finalization steps 16 | type Finalize struct { 17 | Name string `json:"name"` 18 | Type string `json:"type"` 19 | Content []byte // The entire module unparsed as a []byte, used by plugins 20 | } 21 | 22 | // Configuration for including other modules or recipes 23 | type IncludesModule struct { 24 | Name string `json:"name"` 25 | Type string `json:"type"` 26 | Includes []string `json:"includes"` 27 | } 28 | 29 | // Information for building a module 30 | type ModuleCommand struct { 31 | Name string 32 | Command []string 33 | Workdir string 34 | } 35 | 36 | // Configuration for a plugin 37 | type Plugin struct { 38 | Name string 39 | BuildFunc func(*C.char, *C.char, *C.char) string 40 | LoadedPlugin uintptr 41 | PluginInfo api.PluginInfo 42 | } 43 | -------------------------------------------------------------------------------- /docs/articles/en/built-in-modules.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Built-in modules 3 | Description: Learn about the built-in modules that come with Vib and how to use them in your recipes. 4 | PublicationDate: 2024-02-13 5 | Listed: true 6 | Authors: 7 | - mirkobrombin 8 | Tags: 9 | - modules 10 | --- 11 | 12 | > **Note** 13 | > At the time of writing, Vib is in active development and the list of built-in modules may grow over time. This article covers the modules available in Vib v0.8.1. 14 | 15 | Vib supports a variety of built-in modules that you can use to build your recipes. These modules are designed to automate common tasks, such as installing packages, building software, and running custom scripts. 16 | 17 | Before proceeding, make sure to familiarize yourself with [how modules work](/vib/en/use-modules) since this article assumes you have a basic understanding of the module structure and how to use them in your recipes. 18 | 19 | To keep this article concise, we'll cover only the fields that are specific to each module type, so `name`, `type` and `source` will be omitted if they don't have any specific fields. 20 | 21 | ## Summary 22 | 23 | - [Package manager](#package-manager) 24 | - [CMake](#cmake) 25 | - [Dpkg-buildpackage](#dpkg-buildpackage) 26 | - [Dpkg](#dpkg) 27 | - [Go](#go) 28 | - [Make](#make) 29 | - [Meson](#meson) 30 | - [Shell](#shell) 31 | - [Flatpak](#flatpak) 32 | 33 | ## Package manager 34 | 35 | This module allow to install packages using the package manager using the repositories configured in the image. You can change the package manager by changing the value of the `type` field. The following are currently supported: 36 | 37 | - `apt`: Debian-based systems. 38 | - `dnf`: Red Hat-based systems. 39 | 40 | The following specific fields are available: 41 | 42 | - `source`: Defines the source of the packages. 43 | 44 | ### Example 45 | 46 | ```yaml 47 | - name: install-utils 48 | type: apt # or any other supported package manager 49 | sources: 50 | packages: 51 | - curl 52 | - git 53 | ``` 54 | 55 | In the context of this module, this directive also supports the `packages` and `paths` fields. The `packages` field is a list of package names to install, while the `paths` field is a list of paths to `.inst` files containing package names each on a new line: 56 | 57 | ```yaml 58 | - name: install-utils 59 | type: apt # or any other supported package manager 60 | sources: 61 | - path: 62 | - "./utils.inst" 63 | - path: 64 | - "./more-utils.inst" 65 | ``` 66 | 67 | where `utils.inst` and `more-utils.inst` follow the format: 68 | 69 | ```plaintext 70 | curl 71 | git 72 | ``` 73 | 74 | ### Apt 75 | 76 | > **Note** 77 | > The following options requires Vib v.0.5.0 or later. 78 | 79 | The `apt` module, has some additional fields under the `options` key: 80 | 81 | - noRecommends: If set to `true`, the recommended packages will not be installed. 82 | - installSuggestions: If set to `true`, the suggested packages will be installed. 83 | - fixMissing: If set to `true`, the package manager will attempt to fix broken dependencies. 84 | - fixBroken: If set to `true`, the package manager will attempt to fix broken packages. 85 | 86 | ```yaml 87 | - name: install-utils 88 | type: apt 89 | sources: 90 | packages: 91 | - curl 92 | - git 93 | options: 94 | noRecommends: true 95 | installSuggests: true 96 | fixMissing: true 97 | fixBroken: true 98 | ``` 99 | 100 | > **Note** 101 | > The above options if set to `false`, might still be overridden by the package manager's configuration. 102 | 103 | ## CMake 104 | 105 | The CMake module builds a project using the CMake build system. It's suitable for projects that use CMake as their build configuration tool. 106 | 107 | The following specific fields are available: 108 | 109 | - `buildFlags`: Additional flags to pass to the `cmake` command. 110 | 111 | ### Example 112 | 113 | ```yaml 114 | - name: example-cmake-project 115 | type: cmake 116 | buildflags: "-DCMAKE_BUILD_TYPE=Release" 117 | source: 118 | url: "https://example.com/example-project.tar.gz" 119 | type: tar 120 | ``` 121 | 122 | ## Dpkg-buildpackage 123 | 124 | This module builds Debian packages from source using `dpkg-buildpackage` and installs the resulting `.deb` packages. 125 | 126 | The following specific fields are available: 127 | 128 | - `source`: source of the Debian package source code. 129 | 130 | ### Example 131 | 132 | ```yaml 133 | - name: build-deb-package 134 | type: dpkg-buildpackage 135 | source: 136 | url: "https://example.com/package-source.tar.gz" 137 | type: tar 138 | ``` 139 | 140 | ## Go 141 | 142 | The Go module compiles Go projects, allowing for customization through build variables and flags. 143 | 144 | The following specific fields are available: 145 | 146 | - `buildFlags`: Flags for the `go build` command. 147 | 148 | ### Example 149 | 150 | ```yaml 151 | - name: example-go-app 152 | type: go 153 | buildflags: "-v" 154 | source: 155 | url: "https://example.com/go-app-source.tar.gz" 156 | type: tar 157 | ``` 158 | 159 | ## Make 160 | 161 | The Make module automates the build process for projects that use GNU Make. 162 | 163 | The following specific fields are available: 164 | 165 | - `buildCommand`: What command different command for the build, defaults to `make build` 166 | - `intermediateSteps`: Extra commands to run between the build and install command 167 | - `installCommand`: What command to run for installing, defaults to `make install` 168 | 169 | ### Example 170 | 171 | ```yaml 172 | - name: example-make-project 173 | type: make 174 | buildCommand: "make PREFIX=/custompath build" 175 | intermediateSteps: 176 | - "make docs-all -j4" 177 | installCommand: "make DESTDIR=/root install" 178 | sources: 179 | url: "https://example.com/make-project-source.tar.gz" 180 | type: tar 181 | ``` 182 | 183 | ## Meson 184 | 185 | This module is used for building projects configured with the Meson build system. 186 | 187 | The following specific fields are available: 188 | 189 | - `buildFlags`: Additional flags to pass to the `meson` command. 190 | 191 | ### Example 192 | 193 | ```yaml 194 | - name: example-meson-project 195 | type: meson 196 | buildflags: 197 | - "-Dfoo=bar" 198 | sources: 199 | url: "https://example.com/meson-project-source.tar.gz" 200 | type: tar 201 | ``` 202 | 203 | ## Shell 204 | 205 | The Shell module executes arbitrary shell commands, offering the most flexibility for custom operations. 206 | 207 | The following specific fields are available: 208 | 209 | - `commands`: A list of shell commands to execute. 210 | 211 | ### Example 212 | 213 | ```yaml 214 | - name: custom-setup 215 | type: shell 216 | commands: 217 | - "echo Hello, World!" 218 | - "apt update && apt install -y curl" 219 | ``` 220 | 221 | ## Flatpak 222 | 223 | The Flatpak module installs Flatpak packages using the `flatpak` command. 224 | 225 | The following specific fields are available: 226 | 227 | - `system`: If configured, the module will install the applications system-wide. 228 | - `user`: If configured, the module will install the applications user-wide. 229 | 230 | ### Example 231 | 232 | ```yaml 233 | - name: install-flatpak-app 234 | type: flatpak 235 | system: 236 | repourl: "https://flathub.org/repo/flathub.flatpakrepo" 237 | reponame: "flathub" 238 | install: 239 | - "org.gnome.Epiphany" 240 | remove: 241 | - "org.gnome.Epiphany" 242 | user: 243 | repourl: "https://flathub.org/repo/flathub.flatpakrepo" 244 | reponame: "flathub" 245 | install: 246 | - "org.gnome.Epiphany" 247 | remove: 248 | - "org.gnome.Epiphany" 249 | ``` 250 | -------------------------------------------------------------------------------- /docs/articles/en/contributing.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Contributing 3 | Description: We welcome contributions from the community. Learn how to contribute to the Vib project. 4 | PublicationDate: 2024-02-14 5 | Listed: true 6 | Authors: 7 | - mirkobrombin 8 | Tags: 9 | - contributing 10 | - development 11 | --- 12 | 13 | We welcome contributions from the community, Vib is an Open Source project and we are always looking for new features, bug fixes, and improvements. This guide will help you get started with contributing. 14 | 15 | ## How to Contribute 16 | 17 | There are many ways to contribute, from writing documentation and tutorials, to testing, submitting bug reports, or writing code to fix bugs or add features. 18 | 19 | ### Writing Documentation and Tutorials 20 | 21 | If you want to contribute to the documentation, you can do so by submitting a pull request to the [Vib](https://github.com/Vanilla-OS/Vib) repository. The documentation is written in Markdown and is located in the `docs/articles` folder. We use [Chronos](https://github.com/Vanilla-OS/Chronos) to manage the documentation, so make sure to follow the article metadata structure [described here](https://github.com/Vanilla-OS/Chronos/tree/main?tab=readme-ov-file#article-structure). 22 | 23 | Documentation should be clear, concise, and easy to understand. If you are writing a tutorial, make sure to include all the necessary steps to reproduce the tutorial, taking care of documenting terms and concepts that might not be familiar to all readers. 24 | 25 | Provide examples and code snippets to help the reader understand the concepts you are explaining, use illustrations only to illustrate complex structures or concepts, for stuff like the structure of a folder use a code based representation instead. 26 | 27 | ### Testing and Submitting Bug Reports 28 | 29 | If you find a bug in Vib, please submit an issue to the [Vib](https://github.com/Vanilla-OS/Vib/issues) repository. Before submitting a bug report, make sure to check if it has already been reported, and if not, provide as much information as possible to help us reproduce the issue. 30 | 31 | Bug reports are very important to us, and we appreciate the time you take to submit them. Just make sure to report bugs in the context of the Vib project, if you are using a recipe and you find a bug in it, please report it to the recipe repository. 32 | 33 | ### Writing Code 34 | 35 | If you are a developer and want to contribute to the Vib project by writing code, you can do so by submitting a pull request to the [GitHub repository](https://github.com/Vanilla-OS/Vib). Before writing code, make sure to check if the feature you want to implement is already being worked on, and if not, open an issue to discuss it with the maintainers or join our [Discord](https://vanillaos.org/community) to discuss it with the community, look for the `#vib` channel. 36 | 37 | We appreciate your time and effort in contributing to the Vib project, we would be sorry if you write code that we cannot merge, so make sure to discuss your ideas before starting. 38 | 39 | #### Extending Built-in Modules 40 | 41 | If you want to add a new built-in module to Vib, please consider the following: 42 | 43 | - **Is it really necessary?** Make sure that the module you want to add is not already doable with the existing modules, and that it is a common use case, if the process is too complex using the available modules, then it might be worth it. Modules should be generic and reusable, if you need a module for a specific use case, consider writing a plugin instead. 44 | - **Does it require a new dependency?** If the module you want to add requires a new dependency, make sure that it is a widely used library, and that it is not too heavy. We want to keep Vib as lightweight as possible. 45 | - **Use self-explanatory names**: The name of the module should be self-explanatory, and it should be as short as possible, while still being descriptive. The same applies to the module's parameters, for example, if you are writing a module to copy files (that should not be the case since a shell module is good enough for that), the parameters should be `from` and `to`, not `originalPath` and `destinationPath`, while both are correct, the first is more concise and easier to understand. 46 | - **Distro-agnostic first**: If the module you want to add is distro-agnostic, it is more likely to be accepted, if it is not, make sure to provide a good reason for it. 47 | 48 | #### Code of Conduct 49 | 50 | When contributing to this project, please make sure to read and follow our [Code of Conduct](https://vanillaos.org/code-of-conduct). This document helps us to create a welcoming and inclusive community, and we expect all contributors to follow it. 51 | 52 | #### Contribution Guidelines 53 | 54 | Before contributing, please make sure to read our [Contribution Guidelines](https://github.com/Vanilla-OS/.github/blob/main/CONTRIBUTING.md), to learn how to write commits, submit pull requests, and more. 55 | -------------------------------------------------------------------------------- /docs/articles/en/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Getting Started 3 | Description: How to start using Vib to build your Container images. 4 | PublicationDate: 2024-12-28 5 | Listed: true 6 | Authors: 7 | - mirkobrombin 8 | - kbdharun 9 | - surinameclubcard 10 | Tags: 11 | - getting-started 12 | --- 13 | 14 | Vib is a powerful tool that allows you to create container images using a YAML recipe that defines the necessary steps to build the image through the use of predefined or custom modules. 15 | 16 | ## Requirements 17 | 18 | To use Vib, there are no specific requirements; you just need a Linux\* operating system (Mac OS and Windows will be supported in the future). Optionally, you can install a container engine to test and publish the images created to a registry. 19 | 20 | \* Currently, Vib requires a Linux distribution with `glibc`. 21 | 22 | ### Supported Container Engines 23 | 24 | - [Docker](https://www.docker.com/) 25 | - [Podman](https://podman.io/) 26 | 27 | Other container engines might work but have not been tested. If you have tested Vib with another container engine, please report it to our community. 28 | 29 | ## Installation 30 | 31 | Vib is distributed as a single binary, so there's no need to install any runtime or dependencies. You can download the latest version of Vib from the [GitHub releases page](https://github.com/Vanilla-OS/Vib). In addition to this, Vib has official plugins which are used for all the Vanilla-OS images, they can also be downlaoded from the [Github releases page](https://github.com/Vanilla-OS/Vib) as the `plugins.tar.xz` archvie. Once downloaded, make `vib` executable and move it to a directory included in your `PATH`. Vib searches for plugins in a global search path at `/usr/share/vib/plugins/` and inside the `plugins` directory in your project directory. It is recommended to extract `plugins.tar.xz` to `/usr/share/vib/plugins/` as they are considered core vib plugins and may be used by a lot of images. 32 | 33 | The following commands will allow you to download and install Vib: 34 | 35 | ```bash 36 | wget https://github.com/Vanilla-OS/Vib/releases/latest/download/vib 37 | chmod +x vib 38 | mv vib ~/.local/bin 39 | ``` 40 | 41 | If wget is not installed, you can use curl: 42 | 43 | ```bash 44 | curl -SLO https://github.com/Vanilla-OS/Vib/releases/latest/download/vib 45 | chmod +x vib 46 | mv vib ~/.local/bin 47 | ``` 48 | 49 | The following commands for the plugins: 50 | 51 | ```bash 52 | wget https://github.com/Vanilla-OS/Vib/releases/latest/download/plugins.tar.xz 53 | sudo mkdir -p /usr/share/vib/plugins 54 | sudo tar -xvf plugins.tar.xz -C /usr/share/vib/plugins/ 55 | ``` 56 | 57 | Or with curl: 58 | 59 | ```bash 60 | curl -SLO https://github.com/Vanilla-OS/Vib/releases/latest/download/plugins.tar.xz 61 | sudo mkdir -p /usr/share/vib/plugins 62 | sudo tar -xvf plugins.tar.xz -C /usr/share/vib/plugins 63 | ``` 64 | 65 | ## Usage 66 | 67 | To start using Vib, create a `vib.yml` file in a new directory. This file will contain the recipe for your container image. 68 | 69 | ```bash 70 | mkdir my-vib-project 71 | cd my-vib-project 72 | touch vib.yml 73 | ``` 74 | 75 | Here's an example `vib.yml` file: 76 | 77 | ```yml 78 | name: my-recipe 79 | id: my-node-app 80 | stages: 81 | - id: build 82 | base: node:current-slim 83 | labels: 84 | maintainer: My Awesome Team 85 | args: 86 | DEBIAN_FRONTEND: noninteractive 87 | expose: 88 | "3000": "" 89 | entrypoint: 90 | exec: 91 | - node 92 | - /app/app.js 93 | runs: 94 | commands: 95 | - echo 'APT::Install-Recommends "0";' > /etc/apt/apt.conf.d/01norecommends 96 | modules: 97 | - name: build-app 98 | type: shell 99 | source: 100 | type: git 101 | url: https://github.com/mirkobrombin/node-sample 102 | branch: main 103 | commit: latest 104 | commands: 105 | - mv /sources/build-app /app 106 | - cd /app 107 | - npm i 108 | - npm run build 109 | ``` 110 | 111 | In this example, we're creating a container image with one stage based on `node:current-slim` with some custom labels and environment variables. The image uses a single module to build a Node.js application from a Git repository. The application is then installed and built using `npm`. Then it exposes the port `3000` and sets the entrypoint to node `/app/app.js`. 112 | 113 | Once you've created the `vib.yml` file, you can run the command: 114 | 115 | ```bash 116 | vib build vib.yml 117 | ``` 118 | 119 | to turn your recipe into a Containerfile. Use that file to build the container image with your container engine. To streamline the process, you can use the `compile` command to build the container image directly: 120 | 121 | ```bash 122 | vib compile --runtime docker 123 | ``` 124 | 125 | changing `docker` with the container engine you have installed. Both `docker` and `podman` are supported. If you leave out the `--runtime` flag, Vib will use the default container engine giving priority to Docker. 126 | 127 | > **Note:** 128 | > On a Vanilla OS host, you need to run `vib compile` from the `host-shell`. 129 | 130 | The generated `Containerfile` is compatible with both Docker and Podman. 131 | 132 | ## Next Steps 133 | 134 | Now that you've learned how to create a container image with Vib, you can start experimenting with predefined and custom modules to create more complex container images. Check out the [documentation](/collections/vib) for more information on all of Vib's features. 135 | 136 | We recommend starting with the documentation on the [recommended structure of a Vib project](/vib/en/project-structure) to understand how to best leverage Vib in your projects. 137 | -------------------------------------------------------------------------------- /docs/articles/en/github-action.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Build using GitHub Actions 3 | Description: How to build a Vib image using GitHub Actions. 4 | PublicationDate: 2024-02-14 5 | Listed: true 6 | Authors: 7 | - mirkobrombin 8 | - kbdharun 9 | Tags: 10 | - github 11 | - build 12 | --- 13 | 14 | Many projects use GitHub to host their code, and GitHub Actions to automate their workflows. Vib can be integrated into your GitHub Actions workflow to build your images automatically. To streamline the process, you can use the [Vib GitHub Action](https://github.com/Vanilla-OS/vib-gh-action). 15 | 16 | ## Setup the Workflow 17 | 18 | To use the Vib GitHub Action, you need to create a workflow file in the repository where your Vib recipe is located. Create a new file in the `.github/workflows` directory, for example, `vib-build.yml`, and add the following content: 19 | 20 | ```yaml 21 | name: Vib Build 22 | 23 | on: 24 | push: 25 | branches: ["main"] 26 | workflow_dispatch: 27 | 28 | jobs: 29 | build: 30 | runs-on: ubuntu-latest 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - uses: vanilla-os/vib-gh-action@v1.0.0 36 | with: 37 | recipe: "vib.yml" 38 | plugins: "org/repo:tag, org/repo:tag" 39 | 40 | - name: Build the Docker image 41 | run: docker image build -f Containerfile --tag ghcr.io/your_org/your_image:main . 42 | ``` 43 | 44 | Let's break down the workflow file: 45 | 46 | - `name`: The name of the workflow. 47 | - `on`: The events that trigger the workflow. In this case, the workflow runs on every push to the `main` branch and when manually triggered. 48 | - `jobs`: A workflow can contain one or more jobs. In this case, there is only one job called `build`. 49 | - `runs-on`: The type of machine to run the job on. In this case, the job runs on the latest version of Ubuntu; check [here](https://github.com/actions/runner-images?tab=readme-ov-file#available-images) for the available machine types. 50 | - `steps`: The sequence of tasks to run in the job. 51 | - `actions/checkout@v4`: A standard action to check out the repository. 52 | - `vanilla-os/vib-gh-action@v0.7.0`: The Vib GitHub Action to build the image. The `with` section specifies the recipe file and additional plugins to use. 53 | - `run`: Contains a standard command to build the Docker image. The `--tag` option specifies the name and tag of the image, in this case, the tag is `ghcr.io/your_org/your_image:main`, you can change it according to your needs. 54 | 55 | ### Using Custom Plugins 56 | 57 | If you are using custom Vib plugins in your recipe, you can include them in the workflow file. For example, if your plugin is named `my-plugin`, you can add the following step to the workflow file: 58 | 59 | ```yaml 60 | # other steps 61 | - uses: vanilla-os/vib-gh-action@v0.7.0 62 | with: 63 | recipe: "vib.yml" 64 | plugins: "your_org/my-plugin:v0.0.1" 65 | # the rest of the workflow 66 | ``` 67 | 68 | The syntax `your_org/my-plugin:v0.0.1` means: 69 | 70 | - `your_org`: The GitHub organization or user that owns the plugin. 71 | - `my-plugin`: The name of the plugin which is the same as the repository name. 72 | - `v0.0.1`: The version of the plugin to use, which must be a valid tag. 73 | 74 | To use more than one plugin, simply separate them with a comma: 75 | 76 | ```yaml 77 | # other steps 78 | - uses: vanilla-os/vib-gh-action@v0.7.0 79 | with: 80 | recipe: "vib.yml" 81 | plugins: "your_org/my-plugin:v0.0.1, another_org/another-plugin:v1.2.3" 82 | # the rest of the workflow 83 | ``` 84 | 85 | ## Publish the Image to GitHub Container Registry (GHCR) 86 | 87 | The workflow file builds the Docker image to ensure everything is working as expected. If you want to publish the image to the GitHub Container Registry (GHCR), you can rework the workflow file as follows: 88 | 89 | ```yaml 90 | name: Vib Build 91 | 92 | on: 93 | push: 94 | branches: ["main"] 95 | workflow_dispatch: 96 | 97 | env: 98 | REGISTRY_USER: ${{ github.actor }} 99 | REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 100 | 101 | jobs: 102 | build: 103 | runs-on: ubuntu-latest 104 | 105 | steps: 106 | - uses: actions/checkout@v4 107 | 108 | - uses: vanilla-os/vib-gh-action@v0.7.0 109 | with: 110 | recipe: "vib.yml" 111 | 112 | - name: Build the Docker image 113 | run: docker image build -f Containerfile --tag ghcr.io/your_org/your_image:main . 114 | 115 | # Push the image to GHCR (Image Registry) 116 | - name: Push To GHCR 117 | if: github.repository == 'your_org/your_repo' 118 | run: | 119 | docker login ghcr.io -u ${{ env.REGISTRY_USER }} -p ${{ env.REGISTRY_PASSWORD }} 120 | docker image push "ghcr.io/your_org/your_image:main" 121 | ``` 122 | 123 | In this case, the `REGISTRY_USER` and `REGISTRY_PASSWORD` environment variables are set to the GitHub actor and the GitHub token, respectively. The `docker login` command uses these credentials to authenticate with GHCR, and the `docker image push` command pushes the image to the registry. 124 | -------------------------------------------------------------------------------- /docs/articles/en/making-plugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Making a Build Plugin 3 | Description: How to create a custom build plugin for Vib. 4 | PublicationDate: 2024-02-14 5 | Listed: true 6 | Authors: 7 | - mirkobrombin 8 | - axtloss 9 | Tags: 10 | - github 11 | - build 12 | --- 13 | 14 | Vib supports custom, user-made plugins to expand the functionality of modules. 15 | 16 | ## Types of Plugin 17 | 18 | There are two types of plugins currently supported: 19 | 20 | - Build Plugins 21 | - Finalize Plugins 22 | 23 | Build Plugins can be used in modules, they create commands that are part of the Containerfile. 24 | 25 | Finalize Plugins are run after the image has been built, they allow things such as generating isos from the filesystem or directly uploading the built image to a registry. 26 | 27 | This article will focus on Build Plugins, Finalize Plugins are built differently, and will get their own documentation soon. 28 | 29 | ## Plugin Requirements 30 | 31 | Plugins are built as shared object files. In the `go` language, this can be achieved using the `-buildmode=c-shared` flag, while `gcc` requires the `-shared` flag. This way it is possible to write plugins in any compiled language capable of generating shared object files. Plugins can also be created using other languages, though additional steps may be necessary (details on that later). 32 | 33 | The primary communication between `vib` and the plugin is handled through structs serialized to JSON. 34 | 35 | Each build plugin must implement the following functions: 36 | 37 | | Function Name | Arguments | Return Type | Description | 38 | |---------------|-----------|-------------|-------------| 39 | | `PlugInfo` | | `char*` | Returns information about the plugin, typically as a JSON string. | 40 | | `BuildModule` | `moduleInterface *char`, `recipeInterface *char` | `char*` | The main entry point for the plugin. Called by `vib` to retrieve the command to be executed. The command is returned as a JSON string. | 41 | 42 | ### char* PlugInfo() 43 | 44 | This function returns information about the plugin, most notably the type of plugin. 45 | 46 | Plugins that do not define this function are considered deprecated, while they still work, support may be dropped in future releases. 47 | 48 | The function returns the `api.PluginInfo` struct serialised as a json: 49 | 50 | ```json 51 | { 52 | "name": "", 53 | "type": 0, 54 | "usecontainercmds": 0/1 55 | } 56 | ``` 57 | 58 | Vib gets the plugin type from the `type` field: `0` means `BuildPlugin`, and `1` means `FinalizePlugin`. For this article, it should be set to `0`, as it does not cover the requirements for a finalize plugin. 59 | 60 | `usecontainercmds` tells vib whether the plugin adds the relevant containerfile directives itself, or if vib should automatically prepend `CMD` to them, this allows plugins to do more advanced things outside of just specifing commands to run. 61 | 62 | example function: 63 | 64 | ```C 65 | char* PlugInfo() { 66 | return "{\"name\":\"example\",\"type\":0,\"usecontainercmds\":0}"; 67 | } 68 | ``` 69 | 70 | ### char* BuildModule(char* moduleInterface, char\* recipeInterface) 71 | 72 | This is the entry point for plugins that vib calls. It returns a string prefixed with `ERROR:` if an error occurs, otherwise it returns the commands generated for the module. 73 | 74 | The `moduleInterface` argument is a json serialised version of the module defined in the recipe. 75 | 76 | The `recipeInterface` argument is a json serialised version of the entire recipe. 77 | 78 | example function: 79 | 80 | ```C 81 | char* BuildModule(char* moduleInterface, char* recipeInterface) { 82 | return "echo HAII"; 83 | } 84 | ``` 85 | 86 | ## Making plugins without compiling to so files 87 | 88 | One of the vib plugins is the `shim` plugin, it allows users to use plugins in any scripting languages, or regular executables. 89 | 90 | The plugin writes the moduleInterface and recipeInterface into a temporary directory, the paths are given as arguments to the executable. 91 | 92 | Shim then reads the generated commands from stdout. 93 | 94 | example shim plugin: 95 | 96 | ```bash 97 | #!/usr/bin/bash 98 | username=$(cat $1 | jq .username) 99 | echo "useradd -m ${username} && echo '${username}' | passwd ${username} --stdin" 100 | ``` 101 | 102 | 103 | ## Plugin examples 104 | 105 | We provide a plugin template for plugins written in go in the [vib-plugin repo](https://github.com/Vanilla-OS/vib-plugin). 106 | 107 | Example plugins written in languages other than go can be found in axtlos' [vib-plugins repo](https://github.com/axtloss/vib-plugins/) 108 | 109 | A Rust crate for making plugins can be found in stoorps' [vib-rs repo](https://github.com/stoorps/vib-rs), with an example implementation [here](https://github.com/stoorps/vib-rs/tree/main/examples/example-plugin). 110 | -------------------------------------------------------------------------------- /docs/articles/en/project-structure.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Project Structure 3 | Description: How to structure your Vib project. 4 | PublicationDate: 2024-02-13 5 | Listed: true 6 | Authors: 7 | - mirkobrombin 8 | - kbdharun 9 | Tags: 10 | - project 11 | --- 12 | 13 | Vib only requires a `vib.yml` file to build in the root of your project. However, to take full advantage of Vib, you can follow a specific project structure. 14 | 15 | ## Standard Project 16 | 17 | A project is a directory containing a `vib.yml` file, this is the easiest way to use Vib in your existing projects, whatever their structure is. Then simply run `vib build` to build the image according to your recipe. 18 | 19 | The following is an example of a project structure: 20 | 21 | ```plaintext 22 | my-project/ 23 | ├── vib.yml 24 | ``` 25 | 26 | ## Vib Project 27 | 28 | A Vib project is a directory dedicated to Vib, it can be placed in your existing project or as a standalone directory (e.g. a dedicated repository). It can contain multiple recipes to build different images. Usually, a Vib project also contains a `includes.container` directory with extra files to be included in the image and one or more directories to store the modules used in the recipes. 29 | 30 | The following is an example of a Vib project structure: 31 | 32 | ```plaintext 33 | my-project/ 34 | ├── folder and files of your project 35 | ├── vib/ 36 | │ ├── includes.container/ 37 | │ │ ├── etc/ 38 | │ │ │ └── my-config-file 39 | │ │ ├── usr/ 40 | │ │ │ └── share/ 41 | │ │ │ └── applications/ 42 | │ │ │ └── my-app.desktop 43 | │ ├── modules/ 44 | │ │ ├── node.yaml 45 | │ │ ├── python.yaml 46 | │ │ └── myproject.yaml 47 | │ └── vib.yaml 48 | ``` 49 | 50 | ### Structure Details 51 | 52 | Here are some details about the structure: 53 | 54 | - `vib/` is the directory containing the Vib project. 55 | - `includes.container/` is the directory containing the files to be included in the image. It can contain any file or directory you want to include in the image. The files in this directory will be copied to the root of the image following the same structure. 56 | - `modules/` is the directory containing the modules used in the recipes. You can create as many modules directories as you want, naming them as you prefer. Each module directory contains one or more YAML files, each one representing a module, name them as you prefer. 57 | - `vib.yml` is the recipe file for the image. You can have multiple `vib.yml` files in the same project, each one representing a different image. For example, you can have a `dev.yml` and a `prod.yml` file to build different images for development and production environments, then build them with `vib build dev.yml` and `vib build prod.yml`. 58 | 59 | ### Include Modules in the Recipe 60 | 61 | You can define your modules directly in the recipe file but the above structure is recommended to keep the project organized and to reuse the modules across different recipes. So, once you have defined your modules directories, you can include them in the recipe file using the `include` module: 62 | 63 | ```yml 64 | - name: deps-modules 65 | type: includes 66 | includes: 67 | - modules/node.yml 68 | - modules/python.yml 69 | 70 | - name: proj-modules 71 | type: includes 72 | includes: 73 | - modules/myproject.yml 74 | ``` 75 | 76 | #### Remote Modules 77 | 78 | Vib has support for remote modules, you can include them in the recipe file using the module URL or the `gh` pattern: 79 | 80 | ```yml 81 | - name: deps-modules 82 | type: includes 83 | includes: 84 | - https://my-repo.com/modules/node.yml 85 | - gh:my-org/my-repo:branch:modules/python.yml 86 | ``` 87 | 88 | As you can see in the above example, we are explicitly including each module in the recipe file and not pointing to the whole `modules` directory. This is because the `include` module ensures each module gets included in the exact order you specify, ensuring the build process is predictable. 89 | 90 | ### Usecase of the includes.container Directory 91 | 92 | As mentioned, the `includes.container` directory contains the files to be included in the image. This directory is useful to include files that are not part of the project, for example, configuration files, desktop files, or any other file you want to include in the image. 93 | 94 | This is useful especially when you need to configure the Linux system with custom configuration files or new `systemd` services. 95 | 96 | ### Use the `adds` Directive 97 | 98 | Optionally, you can use the `adds` directive to include more directories and files in the image: 99 | 100 | ```yml 101 | adds: 102 | - extra-files/ 103 | - /etc/my-config-file 104 | ``` 105 | -------------------------------------------------------------------------------- /docs/articles/en/recipe-structure.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Structure of a Vib recipe 3 | Description: Learn about the structure of a Vib recipe. 4 | PublicationDate: 2024-02-13 5 | Listed: true 6 | Authors: 7 | - mirkobrombin 8 | - kbdharun 9 | - lambdaclan 10 | Tags: 11 | - modules 12 | - recipe 13 | --- 14 | 15 | > **Note** 16 | > Stages were introduced in Vib v0.6.0, if you are using an older version, please keep in mind all the stage fields are at the top level of the recipe, so no multiple stages are supported. 17 | 18 | A Vib recipe is a YAML file that contains the instructions to build a container image. It's composed of two blocks: 19 | 20 | - metadata 21 | - stages 22 | 23 | The following is a complete example of a Vib recipe: 24 | 25 | ```yml 26 | # metadata 27 | name: My Image 28 | id: my-image-id 29 | 30 | # stages 31 | stages: 32 | - id: build 33 | base: debian:sid-slim 34 | singlelayer: false 35 | labels: 36 | maintainer: My Awesome Team 37 | adds: 38 | - srcdst: 39 | /extra/path/to/add/1: /path/to/destination/1 40 | # multiple additions 41 | # adds: 42 | # - srcdst: 43 | # /extra/path/to/add/1: /path/to/destination/1 44 | # /extra/path/to/add/2: /path/to/destination/2 45 | # specify working directory for destination 46 | # adds: 47 | # - workdir: /tmp 48 | # srcdst: 49 | # /extra/path/to/add/1: . 50 | # /extra/path/to/add/2: . 51 | args: 52 | - arg1: value1 53 | - arg2: value2 54 | runs: 55 | commands: 56 | - some-random-command --that-must-run --on-top-of-all modules 57 | - another-command --help 58 | # specify working directory for commands 59 | # runs: 60 | # workdir: /app 61 | # commands: 62 | # - cp /tmp/start.sh . 63 | # - start.sh 64 | # copy from host 65 | copy: 66 | - srcdst: 67 | /app/awesome.txt: . 68 | # copy multiple 69 | # copy: 70 | # - srcdst: 71 | # /app/awesome.txt: . 72 | # /tmp/test.txt: . 73 | # specify working directory for destination 74 | # copy: 75 | # - workdir: /tmp 76 | # srcdst: 77 | # /app/awesome.txt: . 78 | # /app/test.txt: . 79 | # - workdir: /etc 80 | # srcdst: 81 | # /app/hello.txt: . 82 | modules: 83 | - name: build 84 | type: go 85 | buildvars: 86 | GO_OUTPUT_BIN: "/path/to/output" 87 | source: 88 | url: https://github.com/my-awesome-team/my-awesome-repo 89 | type: git 90 | branch: main 91 | commit: sdb997f0eeb67deaa5940f7c31a19fe1101d3d49 92 | modules: 93 | - name: build-deps 94 | type: apt 95 | source: 96 | packages: 97 | - golang-go 98 | 99 | - id: dist 100 | base: debian:sid-slim 101 | singlelayer: false 102 | labels: 103 | maintainer: My Awesome Team 104 | expose: 105 | "8080": "tcp" 106 | "8081": "" 107 | entrypoint: 108 | exec: 109 | - /app 110 | # entrypoint command with params 111 | # entrypoint: 112 | # exec: 113 | # - npm 114 | # - run 115 | # specify working directory for entrypoint command 116 | # entrypoint: 117 | # workdir: /app 118 | # exec: 119 | # - npm 120 | # - run 121 | # copy from previous stage 122 | copy: 123 | - from: build 124 | srcdst: 125 | /path/to/output: /app 126 | # copy from previous stage with custom working directory for destination 127 | # copy: 128 | # - workdir: /app 129 | # from: build 130 | # srcdst: 131 | # /path/to/output: . 132 | cmd: 133 | exec: 134 | - /app 135 | # command with params 136 | # cmd: 137 | # exec: 138 | # - npm 139 | # - run 140 | # specify working directory for command 141 | # cmd: 142 | # workdir: /app 143 | # exec: 144 | # - npm 145 | # - run 146 | modules: 147 | - name: run 148 | type: shell 149 | commands: 150 | - ls -la /app 151 | # specify working directory for all module commands 152 | # modules: 153 | # - name: run 154 | # type: shell 155 | # workdir: /app 156 | # commands: 157 | # - ls -la 158 | ``` 159 | 160 | ## Metadata 161 | 162 | The metadata block contains the following mandatory fields: 163 | 164 | - `base`: the base image to start from, can be any Docker image from any registry or even `scratch`. 165 | - `name`: the name of the image. 166 | - `id`: the ID of the image is used to specify an image's unique identifier, it is used by platforms like [Atlas](https://images.vanillaos.org/#/) to identify the image. 167 | - `stages`: a list of stages to build the image, useful to split the build process into multiple stages (e.g. to build the application in one stage and copy the artifacts into another one). 168 | - `vibversion`: the vib version with which this recipe was created, used to avoid vib from processing incompatible recipes 169 | - `includsepath`: an alternative includes path other than `includes.container` 170 | 171 | ## Stages 172 | 173 | Stages are a list of instructions to build an image, useful to split the build process into multiple stages (e.g. to build the application in one stage and copy the artifacts into another one). Each stage is a YAML snippet that defines a set of instructions. 174 | 175 | Each stage has the following fields: 176 | 177 | - `singlelayer`: a boolean value that indicates if the image should be built as a single layer. This is useful in some cases to reduce the size of the image (e.g. when building an image using a rootfs, an example [here](https://github.com/Vanilla-OS/pico-image/blob/5b0e064677f78f6e89d619dcb4df4e585bef378f/recipe.yml)). 178 | - `labels`: a map of labels to apply to the image, useful to add metadata to the image that can be read by the container runtime. 179 | - `adds`: a list of files or directories to add to the image, useful to include files in the image that are not part of the source code (the preferred way to include files in the image is to use the `includes.container/` directory, see [Project Structure](/docs/articles/en/project-structure)). 180 | - `args`: a list of environment variables to set in the image. 181 | - `runs`: a list of commands to run in the image (as an alternative to the `shell` module, useful for dividing the commands of your recipe from those needed to configure the image, for example, to disable the recommended packages in apt). 182 | - `expose`: a list of ports to expose in the image. 183 | - `cmd`: the command to run when the container starts. 184 | - `entrypoint`: the entry point for the container, it's similar to `cmd` but it's not overridden by the command passed to the container at runtime, useful to handle the container as an executable. 185 | - `copy`: a list of files or directories to copy from another stage (or copy from host), useful to copy files from one stage to another. 186 | - `modules`: a list of modules to use in the stage. 187 | - `addincludes`: whether `includes.container` should be copied into this stage 188 | 189 | ### Modules 190 | 191 | The modules block contains a list of modules to use in the recipe. Each module is a YAML snippet that defines a set of instructions. The common structure is: 192 | 193 | ```yml 194 | - name: name-of-the-module 195 | type: type-of-the-module 196 | # specific fields for the module type 197 | ``` 198 | 199 | Refer to the [Use Modules](/vib/en/use-modules) article for more information on how to use modules in a recipe and [Built-in Modules](/vib/en/built-in-modules) for a list of the built-in modules and their specific fields. 200 | 201 | You can also write your custom modules by making a Vib plugin, see the [Making a Plugin](/vib/en/making-plugin) article for more information. 202 | 203 | #### Setting up the working directory 204 | 205 | Each module can have a `workdir` field that changes the directory before executing the rest of the module operations. The following is an example of how to use the `workdir` field: 206 | 207 | ```yml 208 | - name: example-module 209 | type: shell 210 | workdir: /app 211 | commands: 212 | - touch file.txt 213 | 214 | - name: example-module-2 215 | type: shell 216 | workdir: /app 217 | commands: 218 | - ls -la 219 | ``` 220 | 221 | In this example, the `example-module` module creates a file named `file.txt` in the `/app` directory, and the `example-module-2` module lists the contents of the `/app` directory. 222 | 223 | ### Copying files between stages 224 | 225 | You can copy files between stages using the `copy` field. This consists of a list of files or directories to copy from another stage. Each item in the list is a YAML snippet that defines the source and destination of the copy operation. The common structure is: 226 | 227 | ```yml 228 | - from: stage-id-to-copy-from 229 | srcdst: 230 | /path/to/source: /path/to/destination 231 | ``` 232 | 233 | For example, to copy the `/path/to/output` directory from the `build` stage to the `/app` directory in the `dist` stage, you can use the following snippet: 234 | 235 | ```yml 236 | - from: build 237 | srcdst: 238 | /path/to/output: /app 239 | ``` 240 | 241 | so it becomes available in the `dist` stage. 242 | 243 | ### Using a custom working directory (`workdir`) 244 | 245 | The following commands are supported: 246 | 247 | - adds 248 | - workdir sets destination path 249 | - copy 250 | - workdir sets destination path 251 | - runs 252 | - workdir changes directory (cd) before executing command 253 | - cmd 254 | - workdir changes directory (cd) before executing command 255 | - entrypoint 256 | - workdir changes directory (cd) before executing command 257 | - modules 258 | - workdir changes directory (cd) before executing command list 259 | -------------------------------------------------------------------------------- /docs/articles/en/use-modules.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: How to use Vib modules 3 | Description: How to use predefined and custom modules in your Vib recipes. 4 | PublicationDate: 2024-02-13 5 | Listed: true 6 | Authors: 7 | - mirkobrombin 8 | - kbdharun 9 | Tags: 10 | - modules 11 | --- 12 | 13 | Modules are a fundamental part of Vib, likely the thing you will interact with the most. We can see them as the building blocks of your container image, each one performing a specific task. 14 | 15 | ## Familiarize with Vib Recipes 16 | 17 | > **Note** 18 | > Stages were introduced in Vib v0.6.0, if you are using an older version, please keep in mind all the stage fields are at the top level of the recipe, so no multiple stages are supported. 19 | 20 | Before diving into the modules, it's important to understand the structure of a Vib recipe. 21 | 22 | ![Vib Recipe Structure](https://raw.githubusercontent.com/Vanilla-OS/Vib/main/docs/uploads/vib-recipe-structure.png) 23 | 24 | As you can see, a recipe has two main entities: 25 | 26 | - **Recipe**: your recipe, the YAML file that contains the instructions to build the image. 27 | - **Stages**: one or more stages that define a set of operations to perform. Each stage can have a set of modules that will be executed in order. 28 | 29 | Think of a recipe as a floor plan for a house, and the stages as the rooms. Each room has a set of tasks to complete, and each task is a module but all the rooms together make the house, your container image. 30 | 31 | To get more information about the structure of a recipe and its fields, please refer to the [recipe structure](/vib/en/recipe-structure) article. 32 | 33 | ## Architecture of a Module 34 | 35 | A module is a YAML snippet that defines a set of instructions, the common structure is: 36 | 37 | ```yml 38 | name: name-of-the-module 39 | type: type-of-the-module 40 | # specific fields for the module type 41 | ``` 42 | 43 | While the `name` and `type` fields are mandatory, the specific fields depend on the module type. For example, a `shell` module has a `commands` field that contains the shell commands to execute to complete the task. 44 | 45 | You will find that some modules have a common `source` field, this instructs Vib to download a resource required for the module to work: 46 | 47 | ```yml 48 | - name: vanilla-tools 49 | type: shell 50 | sources: 51 | - type: tar 52 | url: https://github.com/Vanilla-OS/vanilla-tools/releases/download/continuous/vanilla-tools.tar.gz 53 | commands: 54 | - mkdir -p /usr/bin 55 | - cp /sources/vanilla-tools/lpkg /usr/bin/lpkg 56 | - cp /sources/vanilla-tools/cur-gpu /usr/bin/cur-gpu 57 | - chmod +x /usr/bin/lpkg 58 | - chmod +x /usr/bin/cur-gpu 59 | ``` 60 | 61 | In the above example we define a `shell` module that downloads a tarball from a GitHub release and then copies the binaries to `/usr/bin`. A source can be of three types: 62 | 63 | - `tar`: a tarball archive. You can also define a `checksum` field to verify the integrity of the downloaded archive using a `sha256` hash. 64 | - `file`: a single file. You can also define a `checksum` field to verify the integrity of the downloaded file using a `sha256` hash. 65 | - `git`: a Git repository. 66 | 67 | In the latter case, you can specify the branch, tag or commit to checkout like this: 68 | 69 | ```yaml 70 | name: apx-gui 71 | type: meson 72 | sources: 73 | - type: git 74 | url: https://github.com/Vanilla-OS/apx-gui 75 | branch: main 76 | commit: latest 77 | modules: 78 | - name: apx-gui-deps-install 79 | type: apt 80 | source: 81 | packages: 82 | - build-essential 83 | - gettext 84 | - libadwaita-1-dev 85 | - meson 86 | ``` 87 | 88 | Supported fields for a git source are: 89 | 90 | - `url`: the address of the repository to clone 91 | - `tag`: the tag to checkout, collides with `branch` and `commit`. 92 | - `branch`: the branch to checkout, collides with `tag`. 93 | - `commit`: the commit to checkout, collides with `tag` and `branch`. It can be a commit hash or `latest` to checkout the latest commit. 94 | 95 | ## Built-in Modules 96 | 97 | Vib comes with a set of predefined modules that you can use in your recipes. You can find the list of available modules in the [list of modules](/vib/en/built-in-modules) article. 98 | 99 | ## Custom Modules via Plugins 100 | 101 | You can also extend Vib with custom modules by writing a plugin. Please refer to [making a plugin](/vib/en/make-plugin) for more information. 102 | -------------------------------------------------------------------------------- /docs/articles/en/vscode-extension.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Working with Vib in Visual Studio Code 3 | Description: Learn how to work with Vib recipes in Visual Studio Code using our extension. 4 | PublicationDate: 2024-02-14 5 | Listed: true 6 | Authors: 7 | - mirkobrombin 8 | - kbdharun 9 | Tags: 10 | - development 11 | - vscode 12 | --- 13 | 14 | Visual Studio Code is a popular code editor that provides a wide range of features to help you write, debug, and deploy your code, other than being highly customizable, it also offers a wide range of extensions to enhance your development experience, for example for working with YAML files. 15 | 16 | Vib recipes are written in YAML, and usually, a standard text editor or the YAML support provided by Visual Studio Code is enough to work with them. However, we have developed a dedicated extension for Visual Studio Code to make working with Vib recipes even easier and more efficient. 17 | 18 | ## Features 19 | 20 | > **Note**: The Vib extension is in its early stages, and we are working to add more features and improvements. If you have any feedback or suggestions, please let us know by opening an issue on the [vib-vscode-ext](https://github.com/Vanilla-OS/vib-vscode-ext) repository. 21 | 22 | The following features are currently available in the Vib extension (version 1.1.0): 23 | 24 | - **Metadata validation**: checks the metadata of the recipe. 25 | - **Modules import**: checks if the paths of a `includes` module are correct. 26 | - **Modules name collision**: checks if the names of the modules are unique. 27 | - **Modules type auto-completion**: suggests the type of the module to use, it works with both built-in and custom modules, for the latter it refers to the content of the `plugins` folder. 28 | 29 | ## Installation 30 | 31 | To install the Vib extension, follow these steps: 32 | 33 | 1. Open Visual Studio Code. 34 | 2. Go to the Extensions view by clicking on the Extensions icon on the bar on the side of the window or by pressing `Ctrl+Shift+X`. 35 | 3. Search for `Vib` in the Extensions view search box. 36 | 4. Click on the `Install` button for the Vib extension. 37 | 38 | ## Usage 39 | 40 | Once the extension gets installed, you can start using it to work with Vib recipes. For it to work, you need to put the following header at the beginning of your recipe: 41 | 42 | ```yml 43 | # vib 44 | ``` 45 | 46 | This header is used to identify the file as a Vib recipe and to enable the extension features. In the future, we plan to have support for our dedicated file extension, but for now, this is the way to go. 47 | -------------------------------------------------------------------------------- /docs/uploads/vib-recipe-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanilla-OS/Vib/c5046b25e9d1a227a05144a61f7b3b5c5049dad7/docs/uploads/vib-recipe-structure.png -------------------------------------------------------------------------------- /example/Containerfile: -------------------------------------------------------------------------------- 1 | # Stage: build 2 | FROM debian:sid-slim AS build 3 | LABEL maintainer='Vanilla OS Contributors' 4 | ARG DEBIAN_FRONTEND=noninteractive 5 | RUN echo 'APT::Install-Recommends "0";' > /etc/apt/apt.conf.d/01norecommends 6 | ADD includes.container / 7 | ADD sources /sources 8 | RUN apt-get update 9 | RUN apt install -y libbtrfs-dev golang-go && apt clean && cd /sources/abroot-git && go build -o /usr/local/bin/abroot 10 | RUN apt install -y xterm && apt clean 11 | # Stage: test 12 | FROM debian:sid-slim AS test 13 | COPY --from=build /usr/local/bin/abroot /usr/local/bin/abroot 14 | LABEL maintainer='Vanilla OS Contributors' 15 | ARG DEBIAN_FRONTEND=noninteractive 16 | RUN echo 'APT::Install-Recommends "0";' > /etc/apt/apt.conf.d/01norecommends 17 | ADD includes.container / 18 | ADD sources /sources 19 | RUN ls -l /usr/local/bin/abroot 20 | -------------------------------------------------------------------------------- /example/example.yml: -------------------------------------------------------------------------------- 1 | name: Vib Example 2 | id: vib-example 3 | stages: 4 | - id: build 5 | base: debian:sid-slim 6 | singlelayer: false 7 | labels: 8 | maintainer: Vanilla OS Contributors 9 | args: 10 | DEBIAN_FRONTEND: noninteractive 11 | runs: 12 | commands: 13 | - echo 'APT::Install-Recommends "0";' > /etc/apt/apt.conf.d/01norecommends 14 | 15 | modules: 16 | - name: update-repo 17 | type: shell 18 | commands: 19 | - apt-get update 20 | 21 | - name: abroot-git 22 | type: go 23 | buildvars: 24 | GO_OUTPUT_BIN: "/usr/local/bin/abroot" 25 | source: 26 | url: https://github.com/vanilla-os/abroot.git 27 | type: git 28 | branch: main 29 | commit: efb997f0eeb67deaa5940f7c31a19fe2101d3d49 30 | modules: 31 | - name: abroot-deps 32 | type: apt 33 | source: 34 | packages: 35 | - libbtrfs-dev 36 | - golang-go 37 | 38 | - name: packages 39 | type: apt 40 | source: 41 | paths: 42 | - inst/00-test 43 | 44 | - name: include-modules 45 | type: includes 46 | includes: 47 | - modules/00-net.yml 48 | - modules/10-editor.yml 49 | - https://raw.githubusercontent.com/Vanilla-OS/core-image/main/modules/00-vanilla-abroot.yml 50 | - gh:vanilla-os/core-image:main:modules/00-vanilla-apx.yml 51 | 52 | - id: test 53 | base: debian:sid-slim 54 | singlelayer: false 55 | labels: 56 | maintainer: Vanilla OS Contributors 57 | args: 58 | DEBIAN_FRONTEND: noninteractive 59 | runs: 60 | commands: 61 | - echo 'APT::Install-Recommends "0";' > /etc/apt/apt.conf.d/01norecommends 62 | copy: 63 | - from: build 64 | paths: 65 | - src: /usr/local/bin/abroot 66 | dst: /usr/local/bin/abroot 67 | 68 | modules: 69 | - name: test 70 | type: shell 71 | commands: 72 | - ls -l /usr/local/bin/abroot -------------------------------------------------------------------------------- /example/inst/00-test.inst: -------------------------------------------------------------------------------- 1 | xterm -------------------------------------------------------------------------------- /example/modules/00-net.yml: -------------------------------------------------------------------------------- 1 | name: net-packages 2 | type: apt 3 | source: 4 | packages: 5 | - wget 6 | - curl -------------------------------------------------------------------------------- /example/modules/10-editor.yml: -------------------------------------------------------------------------------- 1 | name: edit-packages 2 | type: apt 3 | source: 4 | packages: 5 | - nano 6 | - vim -------------------------------------------------------------------------------- /finalize-plugins/Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: all 3 | 4 | PLUGS := $(wildcard *.go) 5 | OBJS := $(PLUGS:go=so) 6 | 7 | all: $(OBJS) 8 | 9 | $(OBJS): %.so: %.go 10 | go build -buildmode=c-shared -a -o ../build/plugins/$@ $< 11 | -------------------------------------------------------------------------------- /finalize-plugins/genimage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "C" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/vanilla-os/vib/api" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | ) 12 | 13 | // Configuration for generating an image 14 | type Genimage struct { 15 | Name string `json:"name"` 16 | Type string `json:"type"` 17 | GenimagePath string `json:"genimagepath"` 18 | Config string `json:"config"` 19 | Rootpath string `json:"rootpath"` 20 | Inputpath string `json:"inputpath"` 21 | Outputpath string `json:"outputpath"` 22 | } 23 | 24 | // Provide plugin information as a JSON string 25 | // 26 | //export PlugInfo 27 | func PlugInfo() *C.char { 28 | plugininfo := &api.PluginInfo{Name: "genimage", Type: api.FinalizePlugin} 29 | pluginjson, err := json.Marshal(plugininfo) 30 | if err != nil { 31 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 32 | } 33 | return C.CString(string(pluginjson)) 34 | } 35 | 36 | // Provide the plugin scope 37 | // 38 | //export PluginScope 39 | func PluginScope() int32 { // int32 is defined as GoInt32 in cgo which is the same as a C int 40 | return api.IMAGENAME | api.FS | api.RECIPE 41 | } 42 | 43 | // Replace placeholders in the path with actual values from ScopeData 44 | // $PROJROOT -> Recipe.ParentPath 45 | // $FSROOT -> FS 46 | func ParsePath(path string, data *api.ScopeData) string { 47 | path = strings.Replace(path, "$PROJROOT", data.Recipe.ParentPath, 1) 48 | path = strings.Replace(path, "$FSROOT", data.FS, 1) 49 | return path 50 | } 51 | 52 | // Complete the build process for a generated image module. 53 | // Find the binary if not specified, replace path placeholders 54 | // in the module paths, and run the command 55 | // with the provided configuration 56 | // 57 | //export FinalizeBuild 58 | func FinalizeBuild(moduleInterface *C.char, extraData *C.char) *C.char { 59 | var module *Genimage 60 | var data *api.ScopeData 61 | 62 | err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module) 63 | if err != nil { 64 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 65 | } 66 | 67 | err = json.Unmarshal([]byte(C.GoString(extraData)), &data) 68 | if err != nil { 69 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 70 | } 71 | 72 | genimage := module.GenimagePath 73 | if genimage == "" { 74 | genimage, err = exec.LookPath("genimage") 75 | if err != nil { 76 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 77 | } 78 | } 79 | 80 | cmd := exec.Command( 81 | genimage, 82 | "--config", 83 | ParsePath(module.Config, data), 84 | "--rootpath", 85 | ParsePath(module.Rootpath, data), 86 | "--outputpath", 87 | ParsePath(module.Outputpath, data), 88 | "--inputpath", 89 | ParsePath(module.Inputpath, data), 90 | ) 91 | cmd.Stdout = os.Stdout 92 | cmd.Stderr = os.Stderr 93 | cmd.Dir = data.Recipe.ParentPath 94 | 95 | err = cmd.Run() 96 | if err != nil { 97 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 98 | } 99 | 100 | return C.CString("") 101 | } 102 | 103 | func main() {} 104 | -------------------------------------------------------------------------------- /finalize-plugins/shell-final.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "C" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/vanilla-os/vib/api" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | ) 12 | 13 | // Configuration for a set of shell commands 14 | type Shell struct { 15 | Name string `json:"name"` 16 | Type string `json:"type"` 17 | Commands []string `json:"commands"` 18 | Cwd string `json:"cwd"` 19 | } 20 | 21 | // Provide plugin information as a JSON string 22 | // 23 | //export PlugInfo 24 | func PlugInfo() *C.char { 25 | plugininfo := &api.PluginInfo{Name: "shell-final", Type: api.FinalizePlugin} 26 | pluginjson, err := json.Marshal(plugininfo) 27 | if err != nil { 28 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 29 | } 30 | return C.CString(string(pluginjson)) 31 | } 32 | 33 | // Provide the plugin scope 34 | // 35 | //export PluginScope 36 | func PluginScope() int32 { // int32 is defined as GoInt32 in cgo which is the same as a C int 37 | return api.IMAGENAME | api.FS | api.RECIPE 38 | } 39 | 40 | // Replace placeholders in the path with actual values from ScopeData 41 | // $PROJROOT -> Recipe.ParentPath 42 | // $FSROOT -> FS 43 | func parsePath(path string, data *api.ScopeData) string { 44 | path = strings.ReplaceAll(path, "$PROJROOT", data.Recipe.ParentPath) 45 | path = strings.ReplaceAll(path, "$FSROOT", data.FS) 46 | return path 47 | } 48 | 49 | // Check if the command is in $PATH or includes a directory path. 50 | // Return the full path if found, otherwise return the command unchanged. 51 | func baseCommand(command string, data *api.ScopeData) string { 52 | commandParts := strings.Split(command, " ") 53 | if strings.Contains(commandParts[0], "/") { 54 | return parsePath(commandParts[0], data) 55 | } else { 56 | command, err := exec.LookPath(commandParts[0]) 57 | if err != nil { 58 | return commandParts[0] 59 | } 60 | return command 61 | } 62 | } 63 | 64 | // Extract and return arguments from a command string 65 | func getArgs(command string, data *api.ScopeData) []string { 66 | commandParts := strings.Split(parsePath(command, data), " ") 67 | return commandParts[1:] 68 | } 69 | 70 | // Generate an executable command by resolving the base command and arguments 71 | // and wrapping them with appropriate syntax for execution. 72 | func genCommand(command string, data *api.ScopeData) []string { 73 | baseCommand := baseCommand(command, data) 74 | args := getArgs(command, data) 75 | return append(append(append([]string{"-c", "'"}, strings.Join(args, " ")), baseCommand), "'") 76 | } 77 | 78 | // Execute shell commands from a Shell struct using the provided ScopeData. 79 | // It parses and runs each command in the context of the provided working directory, 80 | // or the recipe's parent path if no specific directory is given. 81 | // 82 | //export FinalizeBuild 83 | func FinalizeBuild(moduleInterface *C.char, extraData *C.char) *C.char { 84 | var module *Shell 85 | var data *api.ScopeData 86 | 87 | err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module) 88 | if err != nil { 89 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 90 | } 91 | 92 | err = json.Unmarshal([]byte(C.GoString(extraData)), &data) 93 | if err != nil { 94 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 95 | } 96 | 97 | for _, command := range module.Commands { 98 | fmt.Println("shell-final:: bash ", "-c ", command) 99 | 100 | cmd := exec.Command( 101 | "bash", "-c", parsePath(command, data), 102 | ) 103 | cmd.Stdin = os.Stdin 104 | cmd.Stdout = os.Stdout 105 | cmd.Stderr = os.Stderr 106 | cmd.Env = os.Environ() 107 | if len(strings.TrimSpace(module.Cwd)) == 0 { 108 | cmd.Dir = data.Recipe.ParentPath 109 | } else { 110 | cmd.Dir = parsePath(module.Cwd, data) 111 | } 112 | 113 | err = cmd.Run() 114 | if err != nil { 115 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 116 | } 117 | } 118 | 119 | return C.CString("") 120 | } 121 | 122 | func main() {} 123 | -------------------------------------------------------------------------------- /finalize-plugins/sysext.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "C" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/vanilla-os/vib/api" 13 | ) 14 | 15 | // Configuration for system extensions 16 | type Sysext struct { 17 | Name string `json:"name"` 18 | Type string `json:"type"` 19 | OSReleaseID string `json:"osreleaseid"` 20 | OSReleaseVersionID string `json:"osreleaseversionid"` 21 | } 22 | 23 | // Provide plugin information as a JSON string 24 | // 25 | //export PlugInfo 26 | func PlugInfo() *C.char { 27 | plugininfo := &api.PluginInfo{Name: "sysext", Type: api.FinalizePlugin} 28 | pluginjson, err := json.Marshal(plugininfo) 29 | if err != nil { 30 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 31 | } 32 | return C.CString(string(pluginjson)) 33 | } 34 | 35 | // Provide the plugin scope 36 | // 37 | //export PluginScope 38 | func PluginScope() int32 { // int32 is defined as GoInt32 in cgo which is the same as a C int 39 | return api.IMAGENAME | api.FS | api.RECIPE 40 | } 41 | 42 | // Process and finalize the build by creating an extension release file and 43 | // creating a SquashFS image from the filesystem 44 | // 45 | //export FinalizeBuild 46 | func FinalizeBuild(moduleInterface *C.char, extraData *C.char) *C.char { 47 | var module *Sysext 48 | var data *api.ScopeData 49 | 50 | err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module) 51 | if err != nil { 52 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 53 | } 54 | 55 | err = json.Unmarshal([]byte(C.GoString(extraData)), &data) 56 | if err != nil { 57 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 58 | } 59 | 60 | var extensionRelease strings.Builder 61 | fmt.Fprintf(&extensionRelease, "ID=%s\n", module.OSReleaseID) 62 | fmt.Fprintf(&extensionRelease, "VERSION_ID=%s\n", module.OSReleaseVersionID) 63 | 64 | err = os.MkdirAll(filepath.Join(data.FS, "usr/lib/extension-release.d"), 0o777) 65 | if err != nil { 66 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 67 | } 68 | err = os.WriteFile(filepath.Join(data.FS, fmt.Sprintf("usr/lib/extension-release.d/extension-release.%s", data.Recipe.Id)), []byte(extensionRelease.String()), 0o777) 69 | if err != nil { 70 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 71 | } 72 | 73 | mksquashfs, err := exec.LookPath("mksquashfs") 74 | if err != nil { 75 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 76 | } 77 | cmd := exec.Command( 78 | mksquashfs, data.FS, 79 | filepath.Join(data.Recipe.ParentPath, fmt.Sprintf("%s.raw", data.Recipe.Id)), 80 | ) 81 | cmd.Stdout = os.Stdout 82 | cmd.Stderr = os.Stderr 83 | cmd.Dir = data.Recipe.ParentPath 84 | 85 | err = cmd.Run() 86 | if err != nil { 87 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 88 | } 89 | 90 | return C.CString("") 91 | } 92 | 93 | func main() {} 94 | -------------------------------------------------------------------------------- /finalize-plugins/systemd-repart.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "C" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/vanilla-os/vib/api" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | ) 12 | 13 | // Configuration for systemd repartitioning 14 | type SystemdRepart struct { 15 | Name string `json:"name"` 16 | Type string `json:"type"` 17 | Output string `json:"output"` 18 | Json string `json:"json"` 19 | SpecOutput string `json:"spec_output"` 20 | Size string `json:"size"` 21 | Seed string `json:"seed"` 22 | Split bool `json:"split"` 23 | Empty string `json:"empty"` 24 | Root string `json:"root"` 25 | DeferPartitions []string `json:"defer_partitions"` 26 | } 27 | 28 | // Provide plugin information as a JSON string 29 | // 30 | //export PlugInfo 31 | func PlugInfo() *C.char { 32 | plugininfo := &api.PluginInfo{Name: "systemd-repart", Type: api.FinalizePlugin} 33 | pluginjson, err := json.Marshal(plugininfo) 34 | if err != nil { 35 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 36 | } 37 | return C.CString(string(pluginjson)) 38 | } 39 | 40 | // Provide the plugin scope 41 | // 42 | //export PluginScope 43 | func PluginScope() int32 { // int32 is defined as GoInt32 in cgo which is the same as a C int 44 | return api.IMAGENAME | api.FS | api.RECIPE 45 | } 46 | 47 | // Replace placeholders in the path with actual values from ScopeData 48 | // $PROJROOT -> Recipe.ParentPath 49 | // $FSROOT -> FS 50 | func parsePath(path string, data *api.ScopeData) string { 51 | path = strings.ReplaceAll(path, "$PROJROOT", data.Recipe.ParentPath) 52 | path = strings.ReplaceAll(path, "$FSROOT", data.FS) 53 | return path 54 | } 55 | 56 | // Finalize the build by executing systemd-repart with the provided configuration 57 | // to generate and apply partitioning specifications and output results 58 | // 59 | //export FinalizeBuild 60 | func FinalizeBuild(moduleInterface *C.char, extraData *C.char) *C.char { 61 | var module *SystemdRepart 62 | var data *api.ScopeData 63 | 64 | err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module) 65 | if err != nil { 66 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 67 | } 68 | 69 | err = json.Unmarshal([]byte(C.GoString(extraData)), &data) 70 | if err != nil { 71 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 72 | } 73 | 74 | repart, err := exec.LookPath("systemd-repart") 75 | if err != nil { 76 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 77 | } 78 | 79 | if len(strings.TrimSpace(module.Json)) == 0 { 80 | module.Json = "off" 81 | } 82 | 83 | if len(strings.TrimSpace(module.Empty)) == 0 { 84 | module.Empty = "create" 85 | } 86 | 87 | if len(strings.TrimSpace(module.Root)) == 0 { 88 | module.Root = data.FS 89 | } else { 90 | module.Root = parsePath(module.Root, data) 91 | } 92 | 93 | args := []string{ 94 | "--definitions=definitions", 95 | fmt.Sprintf("--empty=%s", module.Empty), 96 | fmt.Sprintf("--size=%s", module.Size), 97 | "--dry-run=no", 98 | "--discard=no", 99 | "--offline=true", 100 | "--no-pager", 101 | fmt.Sprintf("--split=%t", module.Split), 102 | fmt.Sprintf("--seed=%s", module.Seed), 103 | fmt.Sprintf("--root=%s", data.FS), 104 | module.Output, 105 | fmt.Sprintf("--json=%s", module.Json), 106 | } 107 | 108 | if len(module.DeferPartitions) > 0 { 109 | args = append(args, fmt.Sprintf("--defer-partitions=%s", strings.Join(module.DeferPartitions, ","))) 110 | } 111 | 112 | cmd := exec.Command( 113 | repart, 114 | args..., 115 | ) 116 | jsonFile, err := os.Create(module.SpecOutput) 117 | if err != nil { 118 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 119 | } 120 | defer jsonFile.Close() 121 | cmd.Stdout = jsonFile 122 | cmd.Stderr = os.Stderr 123 | cmd.Dir = data.Recipe.ParentPath 124 | 125 | err = cmd.Run() 126 | if err != nil { 127 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 128 | } 129 | 130 | return C.CString("") 131 | } 132 | 133 | func main() {} 134 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vanilla-os/vib 2 | 3 | go 1.24.0 4 | 5 | require github.com/spf13/cobra v1.9.1 6 | 7 | require ( 8 | github.com/containers/storage v1.57.1 9 | github.com/mitchellh/mapstructure v1.5.0 10 | ) 11 | 12 | require ( 13 | github.com/BurntSushi/toml v1.4.0 // indirect 14 | github.com/Microsoft/go-winio v0.6.2 // indirect 15 | github.com/Microsoft/hcsshim v0.12.9 // indirect 16 | github.com/containerd/cgroups/v3 v3.0.3 // indirect 17 | github.com/containerd/errdefs v0.3.0 // indirect 18 | github.com/containerd/errdefs/pkg v0.3.0 // indirect 19 | github.com/containerd/typeurl/v2 v2.2.0 // indirect 20 | github.com/cyphar/filepath-securejoin v0.3.6 // indirect 21 | github.com/docker/go-units v0.5.0 // indirect 22 | github.com/gogo/protobuf v1.3.2 // indirect 23 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 24 | github.com/google/go-intervals v0.0.2 // indirect 25 | github.com/hashicorp/errwrap v1.1.0 // indirect 26 | github.com/hashicorp/go-multierror v1.1.1 // indirect 27 | github.com/json-iterator/go v1.1.12 // indirect 28 | github.com/klauspost/compress v1.17.11 // indirect 29 | github.com/klauspost/pgzip v1.2.6 // indirect 30 | github.com/kr/pretty v0.3.1 // indirect 31 | github.com/mistifyio/go-zfs/v3 v3.0.1 // indirect 32 | github.com/moby/sys/capability v0.4.0 // indirect 33 | github.com/moby/sys/mountinfo v0.7.2 // indirect 34 | github.com/moby/sys/user v0.3.0 // indirect 35 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 36 | github.com/modern-go/reflect2 v1.0.2 // indirect 37 | github.com/opencontainers/go-digest v1.0.0 // indirect 38 | github.com/opencontainers/runtime-spec v1.2.0 // indirect 39 | github.com/opencontainers/selinux v1.11.1 // indirect 40 | github.com/pkg/errors v0.9.1 // indirect 41 | github.com/rogpeppe/go-internal v1.11.0 // indirect 42 | github.com/sirupsen/logrus v1.9.3 // indirect 43 | github.com/tchap/go-patricia/v2 v2.3.2 // indirect 44 | github.com/ulikunitz/xz v0.5.12 // indirect 45 | github.com/vbatts/tar-split v0.11.7 // indirect 46 | go.opencensus.io v0.24.0 // indirect 47 | golang.org/x/sys v0.29.0 // indirect 48 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect 49 | google.golang.org/grpc v1.67.0 // indirect 50 | google.golang.org/protobuf v1.34.2 // indirect 51 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 52 | ) 53 | 54 | require ( 55 | github.com/google/uuid v1.6.0 // indirect 56 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 57 | github.com/spf13/pflag v1.0.6 // indirect 58 | github.com/vanilla-os/vib/api v0.0.0-20240812130736-2cc767ade5f9 59 | gopkg.in/yaml.v3 v3.0.1 60 | ) 61 | 62 | replace github.com/vanilla-os/vib/api => ./api 63 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= 4 | github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 5 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 6 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 7 | github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg= 8 | github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y= 9 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 10 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 11 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 12 | github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= 13 | github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= 14 | github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= 15 | github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= 16 | github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= 17 | github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= 18 | github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= 19 | github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= 20 | github.com/containers/storage v1.57.1 h1:hKPoFsuBcB3qTzBxa4IFpZMRzUuL5Xhv/BE44W0XHx8= 21 | github.com/containers/storage v1.57.1/go.mod h1:i/Hb4lu7YgFr9G0K6BMjqW0BLJO1sFsnWQwj2UoWCUM= 22 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 23 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 24 | github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= 25 | github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 26 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 27 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 28 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 30 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 31 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 32 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 33 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 34 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 35 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 36 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 37 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 38 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 39 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 40 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 41 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 42 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 43 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 44 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 45 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 46 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 47 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 48 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 49 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 50 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 51 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 52 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 53 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 54 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 55 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 56 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 57 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 58 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 59 | github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM= 60 | github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y= 61 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 62 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 63 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 64 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 65 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 66 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 67 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 68 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 69 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 70 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 71 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 72 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 73 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 74 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 75 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 76 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 77 | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 78 | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 79 | github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= 80 | github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 81 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 82 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 83 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 84 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 85 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 86 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 87 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 88 | github.com/mistifyio/go-zfs/v3 v3.0.1 h1:YaoXgBePoMA12+S1u/ddkv+QqxcfiZK4prI6HPnkFiU= 89 | github.com/mistifyio/go-zfs/v3 v3.0.1/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k= 90 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 91 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 92 | github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= 93 | github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= 94 | github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= 95 | github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= 96 | github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= 97 | github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= 98 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 99 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 100 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 101 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 102 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 103 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 104 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 105 | github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= 106 | github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 107 | github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8= 108 | github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= 109 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 110 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 111 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 112 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 113 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 114 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 115 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 116 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 117 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 118 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 119 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 120 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 121 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 122 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 123 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 124 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 125 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 126 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 127 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 128 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 129 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 130 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 131 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 132 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 133 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 134 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 135 | github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM= 136 | github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= 137 | github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= 138 | github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 139 | github.com/vbatts/tar-split v0.11.7 h1:ixZ93pO/GmvaZw4Vq9OwmfZK/kc2zKdPfu0B+gYqs3U= 140 | github.com/vbatts/tar-split v0.11.7/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= 141 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 142 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 143 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 144 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 145 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 146 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 147 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 148 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 149 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 150 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 151 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 152 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 153 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 154 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 155 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 156 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 157 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 158 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 159 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 160 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 161 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 162 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 163 | golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= 164 | golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= 165 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 166 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 167 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 168 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 169 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 170 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 171 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 172 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 173 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 174 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 175 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 176 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 177 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 178 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 179 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 180 | golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= 181 | golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 182 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 183 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 184 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 185 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 186 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 187 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 188 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 189 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 190 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 191 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 192 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 193 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 194 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 195 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 196 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 197 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 198 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 199 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= 200 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= 201 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 202 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 203 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 204 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 205 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 206 | google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= 207 | google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= 208 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 209 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 210 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 211 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 212 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 213 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 214 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 215 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 216 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 217 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 218 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 219 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 220 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 221 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 222 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 223 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 224 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 225 | gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= 226 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 227 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 228 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 229 | -------------------------------------------------------------------------------- /logo/png/full-mono-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanilla-OS/Vib/c5046b25e9d1a227a05144a61f7b3b5c5049dad7/logo/png/full-mono-dark.png -------------------------------------------------------------------------------- /logo/png/full-mono-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanilla-OS/Vib/c5046b25e9d1a227a05144a61f7b3b5c5049dad7/logo/png/full-mono-light.png -------------------------------------------------------------------------------- /logo/png/full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanilla-OS/Vib/c5046b25e9d1a227a05144a61f7b3b5c5049dad7/logo/png/full.png -------------------------------------------------------------------------------- /logo/png/icon-mono-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanilla-OS/Vib/c5046b25e9d1a227a05144a61f7b3b5c5049dad7/logo/png/icon-mono-dark.png -------------------------------------------------------------------------------- /logo/png/icon-mono-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanilla-OS/Vib/c5046b25e9d1a227a05144a61f7b3b5c5049dad7/logo/png/icon-mono-light.png -------------------------------------------------------------------------------- /logo/png/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanilla-OS/Vib/c5046b25e9d1a227a05144a61f7b3b5c5049dad7/logo/png/icon.png -------------------------------------------------------------------------------- /logo/svg/full-mono-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /logo/svg/full-mono-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /logo/svg/full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /logo/svg/icon-mono-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /logo/svg/icon-mono-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /logo/svg/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "github.com/vanilla-os/vib/cmd" 6 | ) 7 | 8 | var ( 9 | Version = cmd.Version 10 | ) 11 | 12 | func main() { 13 | err := cmd.Execute() 14 | if (err != nil) { 15 | os.Exit(1) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /plugins/Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: all 3 | 4 | PLUGS := $(wildcard *.go) 5 | OBJS := $(PLUGS:go=so) 6 | 7 | all: $(OBJS) 8 | 9 | $(OBJS): %.so: %.go 10 | go build -buildmode=c-shared -a -o ../build/plugins/$@ $< 11 | -------------------------------------------------------------------------------- /plugins/apt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | 9 | "C" 10 | 11 | "github.com/vanilla-os/vib/api" 12 | ) 13 | import ( 14 | "strings" 15 | ) 16 | 17 | // Configuration for an APT module 18 | type AptModule struct { 19 | Name string `json:"name"` 20 | Type string `json:"type"` 21 | Options AptOptions `json:"options"` 22 | Sources []api.Source `json:"sources"` 23 | } 24 | 25 | // Options for APT package management 26 | type AptOptions struct { 27 | NoRecommends bool `json:"no_recommends"` 28 | InstallSuggests bool `json:"install_suggests"` 29 | FixMissing bool `json:"fix_missing"` 30 | FixBroken bool `json:"fix_broken"` 31 | } 32 | 33 | // Provide plugin information as a JSON string 34 | // 35 | //export PlugInfo 36 | func PlugInfo() *C.char { 37 | plugininfo := &api.PluginInfo{Name: "apt", Type: api.BuildPlugin, UseContainerCmds: false} 38 | pluginjson, err := json.Marshal(plugininfo) 39 | if err != nil { 40 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 41 | } 42 | return C.CString(string(pluginjson)) 43 | } 44 | 45 | // Generate an apt-get install command from the provided module and recipe. 46 | // Handle package installation and apply appropriate options. 47 | // 48 | //export BuildModule 49 | func BuildModule(moduleInterface *C.char, recipeInterface *C.char, arch *C.char) *C.char { 50 | var module *AptModule 51 | var recipe *api.Recipe 52 | 53 | err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module) 54 | if err != nil { 55 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 56 | } 57 | 58 | err = json.Unmarshal([]byte(C.GoString(recipeInterface)), &recipe) 59 | if err != nil { 60 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 61 | } 62 | 63 | args := "" 64 | if module.Options.NoRecommends { 65 | args += "--no-install-recommends " 66 | } 67 | if module.Options.InstallSuggests { 68 | args += "--install-suggests " 69 | } 70 | if module.Options.FixMissing { 71 | args += "--fix-missing " 72 | } 73 | if module.Options.FixBroken { 74 | args += "--fix-broken " 75 | } 76 | 77 | packages := "" 78 | for _, source := range module.Sources { 79 | if api.TestArch(source.OnlyArches, C.GoString(arch)) { 80 | if len(source.Packages) > 0 { 81 | for _, pkg := range source.Packages { 82 | packages += pkg + " " 83 | } 84 | } 85 | 86 | if len(strings.TrimSpace(source.Path)) > 0 { 87 | fileInfo, err := os.Stat(source.Path) 88 | if err != nil { 89 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 90 | } 91 | if !fileInfo.Mode().IsRegular() { 92 | continue 93 | } 94 | file, err := os.Open(source.Path) 95 | if err != nil { 96 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 97 | } 98 | defer file.Close() 99 | 100 | scanner := bufio.NewScanner(file) 101 | for scanner.Scan() { 102 | packages += scanner.Text() + " " 103 | } 104 | 105 | if err := scanner.Err(); err != nil { 106 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 107 | } 108 | } 109 | } 110 | } 111 | 112 | if len(packages) >= 1 { 113 | cmd := fmt.Sprintf("apt-get install -y %s %s && apt-get clean", args, packages) 114 | 115 | return C.CString(cmd) 116 | } 117 | 118 | return C.CString("ERROR: no packages or paths specified") 119 | } 120 | 121 | func main() {} 122 | -------------------------------------------------------------------------------- /plugins/cmake.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "C" 5 | "encoding/json" 6 | "fmt" 7 | "path/filepath" 8 | 9 | "github.com/vanilla-os/vib/api" 10 | ) 11 | 12 | // Configuration for a CMake module 13 | type CMakeModule struct { 14 | Name string `json:"name"` 15 | Type string `json:"type"` 16 | BuildVars map[string]string `json:"buildvars"` 17 | BuildFlags string `json:"buildflags"` 18 | Source api.Source 19 | } 20 | 21 | // Provide plugin information as a JSON string 22 | // 23 | //export PlugInfo 24 | func PlugInfo() *C.char { 25 | plugininfo := &api.PluginInfo{Name: "cmake", Type: api.BuildPlugin, UseContainerCmds: false} 26 | pluginjson, err := json.Marshal(plugininfo) 27 | if err != nil { 28 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 29 | } 30 | return C.CString(string(pluginjson)) 31 | } 32 | 33 | // Generate a shell command to build a CMake project based on the provided module and recipe. 34 | // Download and move the source, set up build variables and flags, and construct the CMake build command. 35 | // 36 | //export BuildModule 37 | func BuildModule(moduleInterface *C.char, recipeInterface *C.char, arch *C.char) *C.char { 38 | var module *CMakeModule 39 | var recipe *api.Recipe 40 | 41 | err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module) 42 | if err != nil { 43 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 44 | } 45 | 46 | err = json.Unmarshal([]byte(C.GoString(recipeInterface)), &recipe) 47 | if err != nil { 48 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 49 | } 50 | 51 | if !api.TestArch(module.Source.OnlyArches, C.GoString(arch)) { 52 | return C.CString("") 53 | } 54 | 55 | err = api.DownloadSource(recipe, module.Source, module.Name) 56 | if err != nil { 57 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 58 | } 59 | err = api.MoveSource(recipe.DownloadsPath, recipe.SourcesPath, module.Source, module.Name) 60 | if err != nil { 61 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 62 | } 63 | buildVars := map[string]string{} 64 | for k, v := range module.BuildVars { 65 | buildVars[k] = v 66 | } 67 | 68 | buildFlags := "" 69 | if module.BuildFlags != "" { 70 | buildFlags = " " + module.BuildFlags 71 | } 72 | 73 | cmd := fmt.Sprintf( 74 | "cd /sources/%s && mkdir -p build && cd build && cmake ../%s && make", 75 | filepath.Join(recipe.SourcesPath, api.GetSourcePath(module.Source, module.Name)), 76 | buildFlags, 77 | ) 78 | 79 | return C.CString(cmd) 80 | } 81 | 82 | func main() { fmt.Println("This plugin is not meant to run standalone!") } 83 | -------------------------------------------------------------------------------- /plugins/dpkg-buildpackage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "C" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/vanilla-os/vib/api" 9 | ) 10 | 11 | // Configuration for building a Debian package using dpkg 12 | type DpkgBuildModule struct { 13 | Name string `json:"name"` 14 | Type string `json:"type"` 15 | Source api.Source 16 | } 17 | 18 | // Provide plugin information as a JSON string 19 | // 20 | //export PlugInfo 21 | func PlugInfo() *C.char { 22 | plugininfo := &api.PluginInfo{Name: "dpkg-buildpackage", Type: api.BuildPlugin, UseContainerCmds: false} 23 | pluginjson, err := json.Marshal(plugininfo) 24 | if err != nil { 25 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 26 | } 27 | return C.CString(string(pluginjson)) 28 | } 29 | 30 | // Generate a command to build a Debian package using dpkg and install 31 | // the resulting .deb package. Handle downloading, moving the source, 32 | // and running dpkg-buildpackage with appropriate options. 33 | // 34 | //export BuildModule 35 | func BuildModule(moduleInterface *C.char, recipeInterface *C.char, arch *C.char) *C.char { 36 | var module *DpkgBuildModule 37 | var recipe *api.Recipe 38 | 39 | err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module) 40 | if err != nil { 41 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 42 | } 43 | 44 | err = json.Unmarshal([]byte(C.GoString(recipeInterface)), &recipe) 45 | if err != nil { 46 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 47 | } 48 | 49 | if !api.TestArch(module.Source.OnlyArches, C.GoString(arch)) { 50 | return C.CString("") 51 | } 52 | 53 | err = api.DownloadSource(recipe, module.Source, module.Name) 54 | if err != nil { 55 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 56 | } 57 | err = api.MoveSource(recipe.DownloadsPath, recipe.SourcesPath, module.Source, module.Name) 58 | if err != nil { 59 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 60 | } 61 | 62 | cmd := fmt.Sprintf( 63 | "cd /sources/%s && dpkg-buildpackage -d -us -uc -b", 64 | api.GetSourcePath(module.Source, module.Name), 65 | ) 66 | 67 | cmd += fmt.Sprintf(" && apt install -y --allow-downgrades ../%s*.deb", module.Source.Path) 68 | 69 | cmd += " && apt clean" 70 | return C.CString(cmd) 71 | } 72 | 73 | func main() { fmt.Println("This plugin is not meant to run standalone!") } 74 | -------------------------------------------------------------------------------- /plugins/flatpak.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "C" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/vanilla-os/vib/api" 9 | ) 10 | import ( 11 | "os" 12 | "path" 13 | "strings" 14 | ) 15 | 16 | // Configuration for managing Flatpak repositories and packages 17 | type innerFlatpakModule struct { 18 | Repourl string `json:"repo-url"` 19 | Reponame string `json:"repo-name"` 20 | Install []string `json:"install"` 21 | Remove []string `json:"remove"` 22 | } 23 | 24 | // Configuration for managing Flatpak repositories and packages 25 | // for both system and user contexts 26 | type FlatpakModule struct { 27 | Name string `json:"name"` 28 | Type string `json:"type"` 29 | System innerFlatpakModule `json:"system"` 30 | User innerFlatpakModule `json:"user"` 31 | } 32 | 33 | var SystemService string = ` 34 | [Unit] 35 | Description=Manage system flatpaks 36 | Wants=network-online.target 37 | After=network-online.target 38 | 39 | [Service] 40 | Type=oneshot 41 | ExecStart=/usr/bin/system-flatpak-setup 42 | Restart=on-failure 43 | RestartSec=30 44 | 45 | [Install] 46 | WantedBy=default.target 47 | ` 48 | 49 | var UserService string = ` 50 | [Unit] 51 | Description=Configure Flatpaks for current user 52 | Wants=network-online.target 53 | After=system-flatpak-setup.service 54 | 55 | [Service] 56 | Type=simple 57 | ExecStart=/usr/bin/user-flatpak-setup 58 | Restart=on-failure 59 | RestartSec=30 60 | 61 | [Install] 62 | WantedBy=default.target 63 | ` 64 | 65 | // Provide plugin information as a JSON string 66 | // 67 | //export PlugInfo 68 | func PlugInfo() *C.char { 69 | plugininfo := &api.PluginInfo{Name: "flatpak", Type: api.BuildPlugin, UseContainerCmds: false} 70 | pluginjson, err := json.Marshal(plugininfo) 71 | if err != nil { 72 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 73 | } 74 | return C.CString(string(pluginjson)) 75 | } 76 | 77 | // Generate a command to add a Flatpak remote repository. 78 | // Add appropriate flags for system-wide or user-specific installation. 79 | func createRepo(module innerFlatpakModule, isSystem bool) string { 80 | fmt.Println("Adding remote ", isSystem, " ", module) 81 | command := "flatpak remote-add --if-not-exists" 82 | if isSystem { 83 | command = fmt.Sprintf("%s --system", command) 84 | } else { 85 | command = fmt.Sprintf("%s --user", command) 86 | } 87 | return fmt.Sprintf("%s %s %s", command, module.Reponame, module.Repourl) 88 | } 89 | 90 | // Generate setup commands for Flatpak module configuration. 91 | // Create scripts for system-wide and user-specific Flatpak setups, 92 | // including repository addition, package installation, and service configuration. 93 | // 94 | //export BuildModule 95 | func BuildModule(moduleInterface *C.char, recipeInterface *C.char) *C.char { 96 | var module *FlatpakModule 97 | var recipe *api.Recipe 98 | 99 | err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module) 100 | if err != nil { 101 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 102 | } 103 | 104 | err = json.Unmarshal([]byte(C.GoString(recipeInterface)), &recipe) 105 | if err != nil { 106 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 107 | } 108 | 109 | os.MkdirAll(path.Join(recipe.ParentPath, "includes.container/usr/bin/"), 0o775) 110 | if module.System.Reponame != "" { 111 | syscommands := "#!/usr/bin/env sh" 112 | if module.System.Repourl != "" { 113 | syscommands = fmt.Sprintf("%s\n%s", syscommands, createRepo(module.System, true)) 114 | fmt.Println(syscommands) 115 | } 116 | if len(module.System.Install) != 0 { 117 | syscommands = fmt.Sprintf("%s\nflatpak install --system --noninteractive %s %s", syscommands, module.System.Reponame, strings.Join(module.System.Install, " ")) 118 | } 119 | if len(module.System.Remove) != 0 { 120 | syscommands = fmt.Sprintf("%s\nflatpak uninstall --system --noninteractive %s %s", syscommands, module.User.Reponame, strings.Join(module.System.Remove, " ")) 121 | } 122 | 123 | syscommands = fmt.Sprintf("%s\nsystemctl disable flatpak-system-setup.service", syscommands) 124 | err := os.WriteFile(path.Join(recipe.ParentPath, "includes.container/usr/bin/system-flatpak-setup"), []byte(syscommands), 0o777) 125 | if err != nil { 126 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 127 | } 128 | 129 | } 130 | if module.User.Reponame != "" { 131 | usercommands := "#!/usr/bin/env sh" 132 | if module.User.Repourl != "" { 133 | usercommands = fmt.Sprintf("%s\n%s", usercommands, createRepo(module.User, false)) 134 | fmt.Println(usercommands) 135 | } 136 | if len(module.User.Install) != 0 { 137 | usercommands = fmt.Sprintf("%s\nflatpak install --user --noninteractive %s", usercommands, strings.Join(module.User.Install, " ")) 138 | } 139 | if len(module.User.Remove) != 0 { 140 | usercommands = fmt.Sprintf("%s\nflatpak uninstall --user --noninteractive %s", usercommands, strings.Join(module.User.Remove, " ")) 141 | } 142 | 143 | err := os.WriteFile(path.Join(recipe.ParentPath, "includes.container/usr/bin/user-flatpak-setup"), []byte(usercommands), 0o777) 144 | if err != nil { 145 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 146 | } 147 | } 148 | os.MkdirAll(path.Join(recipe.ParentPath, "includes.container/etc/systemd/user"), 0o775) 149 | os.MkdirAll(path.Join(recipe.ParentPath, "includes.container/etc/systemd/system"), 0o775) 150 | os.WriteFile(path.Join(recipe.ParentPath, "includes.container/etc/systemd/user/flatpak-user-setup.service"), []byte(UserService), 0o666) 151 | os.WriteFile(path.Join(recipe.ParentPath, "includes.container/etc/systemd/system/flatpak-system-setup.service"), []byte(SystemService), 0o666) 152 | 153 | return C.CString("systemctl enable --global flatpak-user-setup.service && systemctl enable --system flatpak-system-setup.service") 154 | } 155 | 156 | func main() { fmt.Println("This plugin is not meant to run standalone!") } 157 | -------------------------------------------------------------------------------- /plugins/go.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "C" 5 | "fmt" 6 | 7 | "github.com/vanilla-os/vib/api" 8 | ) 9 | import "encoding/json" 10 | 11 | // Configuration for building a Go module 12 | type GoModule struct { 13 | Name string `json:"name"` 14 | Type string `json:"type"` 15 | Source api.Source 16 | BuildVars map[string]string 17 | BuildFlags string 18 | } 19 | 20 | // Provide plugin information as a JSON string 21 | // 22 | //export PlugInfo 23 | func PlugInfo() *C.char { 24 | plugininfo := &api.PluginInfo{Name: "go", Type: api.BuildPlugin, UseContainerCmds: false} 25 | pluginjson, err := json.Marshal(plugininfo) 26 | if err != nil { 27 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 28 | } 29 | return C.CString(string(pluginjson)) 30 | } 31 | 32 | // Generate a command to build a Go project. Add options for 33 | // setting the output binary name and location based on the provided buildVars 34 | // and BuildFlags, and handle downloading and moving the source. 35 | // 36 | //export BuildModule 37 | func BuildModule(moduleInterface *C.char, recipeInterface *C.char, arch *C.char) *C.char { 38 | var module *GoModule 39 | var recipe *api.Recipe 40 | 41 | err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module) 42 | if err != nil { 43 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 44 | } 45 | 46 | err = json.Unmarshal([]byte(C.GoString(recipeInterface)), &recipe) 47 | if err != nil { 48 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 49 | } 50 | 51 | if !api.TestArch(module.Source.OnlyArches, C.GoString(arch)) { 52 | return C.CString("") 53 | } 54 | 55 | err = api.DownloadSource(recipe, module.Source, module.Name) 56 | if err != nil { 57 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 58 | } 59 | err = api.MoveSource(recipe.DownloadsPath, recipe.SourcesPath, module.Source, module.Name) 60 | if err != nil { 61 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 62 | } 63 | 64 | buildVars := map[string]string{} 65 | for k, v := range module.BuildVars { 66 | buildVars[k] = v 67 | } 68 | 69 | buildFlags := "" 70 | if module.BuildFlags != "" { 71 | buildFlags = " " + module.BuildFlags 72 | } 73 | 74 | buildVars["GO_OUTPUT_BIN"] = module.Name 75 | if module.BuildVars["GO_OUTPUT_BIN"] != "" { 76 | buildVars["GO_OUTPUT_BIN"] = module.BuildVars["GO_OUTPUT_BIN"] 77 | } 78 | 79 | cmd := fmt.Sprintf( 80 | "cd /sources/%s && go build%s -o %s", 81 | api.GetSourcePath(module.Source, module.Name), 82 | buildFlags, 83 | buildVars["GO_OUTPUT_BIN"], 84 | ) 85 | 86 | return C.CString(cmd) 87 | } 88 | 89 | func main() { fmt.Println("This plugin is not meant to run standalone!") } 90 | -------------------------------------------------------------------------------- /plugins/make.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "C" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/vanilla-os/vib/api" 9 | ) 10 | import "strings" 11 | 12 | // Configuration for building a project using Make 13 | type MakeModule struct { 14 | Name string `json:"name"` 15 | Type string `json:"type"` 16 | BuildCommand string `json:"buildcommand"` 17 | InstallCommand string `json:"installcommand"` 18 | IntermediateSteps []string `json:"intermediatesteps"` 19 | Sources []api.Source `json:"sources"` 20 | } 21 | 22 | // Provide plugin information as a JSON string 23 | // 24 | //export PlugInfo 25 | func PlugInfo() *C.char { 26 | plugininfo := &api.PluginInfo{Name: "make", Type: api.BuildPlugin, UseContainerCmds: false} 27 | pluginjson, err := json.Marshal(plugininfo) 28 | if err != nil { 29 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 30 | } 31 | return C.CString(string(pluginjson)) 32 | } 33 | 34 | // Generate a command to build a Make project. Change directory 35 | // to the source path, run 'make' to build the project, and 'make install' 36 | // to install the built project. Handle downloading and moving the source. 37 | // 38 | //export BuildModule 39 | func BuildModule(moduleInterface *C.char, recipeInterface *C.char, arch *C.char) *C.char { 40 | var module *MakeModule 41 | var recipe *api.Recipe 42 | 43 | err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module) 44 | if err != nil { 45 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 46 | } 47 | 48 | err = json.Unmarshal([]byte(C.GoString(recipeInterface)), &recipe) 49 | if err != nil { 50 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 51 | } 52 | 53 | for _, source := range module.Sources { 54 | if api.TestArch(source.OnlyArches, C.GoString(arch)) { 55 | err = api.DownloadSource(recipe, source, module.Name) 56 | if err != nil { 57 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 58 | } 59 | err = api.MoveSource(recipe.DownloadsPath, recipe.SourcesPath, source, module.Name) 60 | if err != nil { 61 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 62 | } 63 | } 64 | } 65 | 66 | buildCommand := "make" 67 | installCommand := "make install" 68 | intermediateSteps := " && " 69 | 70 | if len(strings.TrimSpace(module.BuildCommand)) != 0 { 71 | buildCommand = module.BuildCommand 72 | } 73 | 74 | if len(strings.TrimSpace(module.InstallCommand)) != 0 { 75 | installCommand = module.InstallCommand 76 | } 77 | 78 | if len(module.IntermediateSteps) != 0 { 79 | intermediateSteps = " && " + strings.Join(module.IntermediateSteps, " && ") + " && " 80 | } 81 | 82 | cmd := "cd /sources/" + api.GetSourcePath(module.Sources[0], module.Name) + " && " + buildCommand + intermediateSteps + installCommand 83 | return C.CString(cmd) 84 | } 85 | 86 | func main() { fmt.Println("This plugin is not meant to run standalone!") } 87 | -------------------------------------------------------------------------------- /plugins/meson.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "C" 5 | "encoding/json" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/vanilla-os/vib/api" 10 | ) 11 | import "crypto/sha1" 12 | 13 | // Configuration for building a Meson project 14 | type MesonModule struct { 15 | Name string 16 | Type string 17 | BuildFlags []string `json:"buildflags"` 18 | Sources []api.Source `json"sources"` 19 | } 20 | 21 | // Provide plugin information as a JSON string 22 | // 23 | //export PlugInfo 24 | func PlugInfo() *C.char { 25 | plugininfo := &api.PluginInfo{Name: "meson", Type: api.BuildPlugin, UseContainerCmds: false} 26 | pluginjson, err := json.Marshal(plugininfo) 27 | if err != nil { 28 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 29 | } 30 | return C.CString(string(pluginjson)) 31 | } 32 | 33 | // Generate a command to build a Meson project. Handle source downloading, moving, 34 | // and use Meson and Ninja build tools with a temporary build directory based on the checksum. 35 | // 36 | //export BuildModule 37 | func BuildModule(moduleInterface *C.char, recipeInterface *C.char, arch *C.char) *C.char { 38 | var module *MesonModule 39 | var recipe *api.Recipe 40 | 41 | err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module) 42 | if err != nil { 43 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 44 | } 45 | 46 | err = json.Unmarshal([]byte(C.GoString(recipeInterface)), &recipe) 47 | if err != nil { 48 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 49 | } 50 | 51 | for _, source := range module.Sources { 52 | if api.TestArch(source.OnlyArches, C.GoString(arch)) { 53 | err = api.DownloadSource(recipe, source, module.Name) 54 | if err != nil { 55 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 56 | } 57 | err = api.MoveSource(recipe.DownloadsPath, recipe.SourcesPath, source, module.Name) 58 | if err != nil { 59 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 60 | } 61 | } 62 | } 63 | 64 | var tmpDir string 65 | if strings.EqualFold(module.Sources[0].Type, "git") == true { 66 | tmpDir = fmt.Sprintf("/tmp/%s-%s", module.Sources[0].Commit, module.Name) 67 | } else if module.Sources[0].Type == "tar" || module.Sources[0].Type == "local" { 68 | tmpDir = fmt.Sprintf("/tmp/%s-%s", module.Sources[0].Checksum, module.Name) 69 | } else { 70 | tmpDir = fmt.Sprintf("/tmp/%s-%s", sha1.Sum([]byte(module.Sources[0].URL)), module.Name) 71 | } 72 | cmd := fmt.Sprintf( 73 | "cd /sources/%s && meson %s %s && ninja -C %s && ninja -C %s install", 74 | api.GetSourcePath(module.Sources[0], module.Name), 75 | strings.Join(module.BuildFlags, " "), 76 | tmpDir, 77 | tmpDir, 78 | tmpDir, 79 | ) 80 | 81 | return C.CString(cmd) 82 | } 83 | 84 | func main() { fmt.Println("This plugin is not meant to run standalone!") } 85 | -------------------------------------------------------------------------------- /plugins/shim.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "C" 8 | 9 | "github.com/vanilla-os/vib/api" 10 | ) 11 | import ( 12 | "os" 13 | "os/exec" 14 | "path/filepath" 15 | ) 16 | 17 | // Configuration for a shim module 18 | type ShimModule struct { 19 | Name string `json:"name"` 20 | Type string `json:"type"` 21 | ShimType string `json:"shimtype"` 22 | } 23 | 24 | // Provide plugin information as a JSON string 25 | // 26 | //export PlugInfo 27 | func PlugInfo() *C.char { 28 | plugininfo := &api.PluginInfo{Name: "shim", Type: api.BuildPlugin, UseContainerCmds: false} 29 | pluginjson, err := json.Marshal(plugininfo) 30 | if err != nil { 31 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 32 | } 33 | return C.CString(string(pluginjson)) 34 | } 35 | 36 | // Generate a command to build a shim module. Create temporary directories, 37 | // write module and recipe data to files, and execute the plugin command with 38 | // the paths to these files. 39 | // 40 | //export BuildModule 41 | func BuildModule(moduleInterface *C.char, recipeInterface *C.char) *C.char { 42 | var module *ShimModule 43 | var recipe *api.Recipe 44 | 45 | err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module) 46 | if err != nil { 47 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 48 | } 49 | 50 | err = json.Unmarshal([]byte(C.GoString(recipeInterface)), &recipe) 51 | if err != nil { 52 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 53 | } 54 | 55 | fmt.Printf("[SHIM] Starting plugin: %s\n", module.ShimType) 56 | 57 | dataDir, err := os.MkdirTemp("", fmt.Sprintf("*-vibshim-%s", module.ShimType)) 58 | if err != nil { 59 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 60 | } 61 | defer os.RemoveAll(dataDir) 62 | 63 | pluginCommand := fmt.Sprintf("%s/%s", recipe.PluginPath, module.ShimType) 64 | modulePath := filepath.Join(dataDir, "moduleInterface") 65 | recipePath := filepath.Join(dataDir, "recipeInterface") 66 | 67 | err = os.WriteFile(modulePath, []byte(C.GoString(moduleInterface)), 0o777) 68 | if err != nil { 69 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 70 | } 71 | err = os.WriteFile(recipePath, []byte(C.GoString(recipeInterface)), 0o777) 72 | if err != nil { 73 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 74 | } 75 | 76 | out, err := exec.Command(pluginCommand, modulePath, recipePath).Output() 77 | if err != nil { 78 | return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) 79 | } 80 | return C.CString(string(out)) 81 | } 82 | 83 | func main() {} 84 | -------------------------------------------------------------------------------- /set_new_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | if [ $# -lt 1 ]; then 6 | echo "usag: $0 " 7 | exit 1 8 | fi 9 | 10 | IFS='.' read -ra VERS <<< ${1##v} 11 | if [ ${#VERS[@]} -lt 3 ]; then 12 | echo "invalid version number" 13 | exit 2 14 | fi 15 | 16 | sed -i "s|var Version = \"0.0.0\"|var Version = \"${1##v}\"|g" cmd/root.go 17 | 18 | sed -i "s|0, 0, 0|${VERS[0]}, ${VERS[1]}, ${VERS[2]}|" core/loader.go 19 | 20 | --------------------------------------------------------------------------------