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 | 
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 |
9 |
--------------------------------------------------------------------------------
/logo/svg/full-mono-light.svg:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/logo/svg/full.svg:
--------------------------------------------------------------------------------
1 |
68 |
--------------------------------------------------------------------------------
/logo/svg/icon-mono-dark.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/logo/svg/icon-mono-light.svg:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/logo/svg/icon.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------